// Win32 F16 — clipboard + drag-and-drop (../../../features/f16-clipboard-dnd.md),
// built on the scaffold (../scaffold/app.d). One process, two roles:
//
// * default role: a window that walks a timer-driven phase schedule —
// 1. paste_startup enumerate + read whatever is on the clipboard at
// launch (the host-bridging probe: preload with
// wl-copy/xclip and see if Wine imported it);
// 2. copy_immediate OpenClipboard/EmptyClipboard/SetClipboardData
// (CF_UNICODETEXT, GMEM_MOVEABLE HGLOBAL) with
// "é漢🎈", then read it back and log the formats
// Windows synthesizes around it;
// 3. spawn a reader (--reader child in the same prefix) proving two
// Wine processes share the prefix clipboard;
// 4. copy_delayed SetClipboardData(CF_UNICODETEXT, NULL) — delayed
// rendering; a reader THREAD demands the data 300 ms
// later and the WM_RENDERFORMAT arrival is timed;
// 5. copy_delayed2 delayed again, demanded by a spawned reader
// PROCESS — cross-process WM_RENDERFORMAT;
// 6. grab a worker takes clipboard ownership ->
// WM_DESTROYCLIPBOARD (ownership loss) +
// WM_CLIPBOARDUPDATE (AddClipboardFormatListener);
// 7. dnd OLE drag-and-drop entirely in-process: a
// hand-rolled (raw vtable) IDataObject + IDropSource
// DoDragDrop'd onto this window's own hand-rolled
// IDropTarget (RegisterDragDrop), CF_HDROP carrying
// a real temp file; the full DragEnter/DragOver/
// Drop negotiation and DROPEFFECT_* are logged;
// 8. destroy with an unrendered delayed format still owned ->
// WM_RENDERALLFORMATS (who pays when the source
// exits) — then clean exit 0.
// * --reader role: opens the clipboard (retry loop), logs the offered
// formats and the CF_UNICODETEXT byte count + content match, exits 0.
//
// COM is done with hand-declared vtables (no druntime ole2 imports): the
// binary layout is the contract under test. OleInitialize — NOT CoInitialize —
// is required for RegisterDragDrop/DoDragDrop (clipboard+DnD live in ole32).
// A watchdog thread + SEH filter guarantee the log survives and the process
// exits 0 whatever OLE does under a headless Wine driver.
module (module) appapp;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stdioD header file for C99 <stdio.h>
pubs.opengroup.org/onlinepubs/009695399/basedefs/stdio.h.html, stdio.h
Source
core/stdc/stdio.d
stdio : (alias) app.snprintf = int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogcsnprintf;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stringD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/string.h.html, string.h
Source
core/stdc/string.d
string : (alias) app.memcmp = int core.stdc.string.memcmp(scope const(void*) s1, scope const(void*) s2, ulong n) pure nothrow @nogcmemcmp, (alias) app.memcpy = void* core.stdc.string.memcpy(return scope void* s1, scope const(void*) s2, ulong n) pure nothrow @nogcmemcpy;
import (package) corecore.(package) core.syssys.(package) core.sys.windowswindows.(module) core.sys.windows.windowsWindows API header module
Translated from MinGW API for MS-Windows 4.0
Source
core/sys/windows/windows.d
windows;
import instrument;
pragma(lib, "ole32");
pragma(lib, "shell32");
// ---------------------------------------------------------------------------
// Win32/OLE declarations missing from druntime's core.sys.windows.
enum UINT (constant) _error_ app.WM_CLIPBOARDUPDATE = __errorWM_CLIPBOARDUPDATE = 0x031D;
enum DWORD (constant) _error_ app.DROPEFFECT_NONE = __errorDROPEFFECT_NONE = 0, (constant) _error_ app.DROPEFFECT_COPY = __errorDROPEFFECT_COPY = 1, (constant) _error_ app.DROPEFFECT_MOVE = __errorDROPEFFECT_MOVE = 2,
(constant) _error_ app.DROPEFFECT_LINK = __errorDROPEFFECT_LINK = 4;
enum HRESULT (constant) _error_ app.DRAGDROP_S_DROP = __errorDRAGDROP_S_DROP = 0x00040100, (constant) _error_ app.DRAGDROP_S_CANCEL = __errorDRAGDROP_S_CANCEL = 0x00040101,
(constant) _error_ app.DRAGDROP_S_USEDEFAULTCURSORS = __errorDRAGDROP_S_USEDEFAULTCURSORS = 0x00040102;
enum HRESULT (constant) _error_ app.E_NOINTERFACE = __error__E_NOINTERFACE = cast(HRESULT) 0x80004002,
(constant) _error_ app.E_NOTIMPL = __error__E_NOTIMPL = cast(HRESULT) 0x80004001,
(constant) _error_ app.DV_E_FORMATETC = __error__DV_E_FORMATETC = cast(HRESULT) 0x80040064,
(constant) _error_ app.OLE_E_ADVISENOTSUPPORTED = __error__OLE_E_ADVISENOTSUPPORTED = cast(HRESULT) 0x80040003;
enum DWORD (constant) _error_ app.DVASPECT_CONTENT = __errorDVASPECT_CONTENT = 1, (constant) _error_ app.TYMED_HGLOBAL = __errorTYMED_HGLOBAL = 1, (constant) _error_ app.DATADIR_GET = __errorDATADIR_GET = 1;
struct (struct) app.FORMATETCFORMATETC
{
ushort (field) ushort app.FORMATETC.cfFormatcfFormat; // CLIPFORMAT
void* (field) void* app.FORMATETC.ptdptd;
DWORD (field) _error_ app.FORMATETC.dwAspectdwAspect;
int (field) int app.FORMATETC.lindexlindex;
DWORD (field) _error_ app.FORMATETC.tymedtymed;
}
struct (struct) app.STGMEDIUMSTGMEDIUM
{
DWORD (field) _error_ app.STGMEDIUM.tymedtymed;
void* (field) void* app.STGMEDIUM.hGlobalhGlobal; // the union collapsed to the one arm used here
void* (field) void* app.STGMEDIUM.pUnkForReleasepUnkForRelease;
}
struct (struct) app.DROPFILESDROPFILES
{
DWORD (field) _error_ app.DROPFILES.pFilespFiles; // offset of the file list from the struct start
POINT (field) _error_ app.DROPFILES.ptpt;
BOOL (field) _error_ app.DROPFILES.fNCfNC;
BOOL (field) _error_ app.DROPFILES.fWidefWide; // TRUE: the list is UTF-16
}
struct (struct) app.POINTLPOINTL
{
LONG (field) _error_ app.POINTL.xx, (field) _error_ app.POINTL.yy;
}
extern (Windows) nothrow @nogc
{
HRESULT app.OleInitializeOleInitialize(void*);
void void app.OleUninitialize() nothrow @nogcOleUninitialize();
HRESULT app.RegisterDragDropRegisterDragDrop(HWND, void*);
HRESULT app.RevokeDragDropRevokeDragDrop(HWND);
HRESULT app.DoDragDropDoDragDrop(void* (parameter) void* dataObjdataObj, void* dropSource, DWORD okEffects,
DWORD* (parameter) DWORD* effecteffect);
void app.ReleaseStgMediumReleaseStgMedium((struct) app.STGMEDIUMSTGMEDIUM*);
UINT app.DragQueryFileWDragQueryFileW(void* hDrop, UINT iFile, wchar* (parameter) wchar* bufbuf, UINT cch);
BOOL app.AddClipboardFormatListenerAddClipboardFormatListener(HWND);
DWORD app.GetClipboardSequenceNumberGetClipboardSequenceNumber();
}
// IIDs (hand-declared; values from the platform SDK headers).
__gshared immutable GUID _error_ app.IID_IUnknownIID_IUnknown =
GUID(0, 0, 0, [0xC0, 0, 0, 0, 0, 0, 0, 0x46]);
__gshared immutable GUID _error_ app.IID_IDataObjectIID_IDataObject =
GUID(0x10e, 0, 0, [0xC0, 0, 0, 0, 0, 0, 0, 0x46]);
__gshared immutable GUID _error_ app.IID_IDropSourceIID_IDropSource =
GUID(0x121, 0, 0, [0xC0, 0, 0, 0, 0, 0, 0, 0x46]);
__gshared immutable GUID _error_ app.IID_IDropTargetIID_IDropTarget =
GUID(0x122, 0, 0, [0xC0, 0, 0, 0, 0, 0, 0, 0x46]);
__gshared immutable GUID _error_ app.IID_IEnumFORMATETCIID_IEnumFORMATETC =
GUID(0x103, 0, 0, [0xC0, 0, 0, 0, 0, 0, 0, 0x46]);
bool app.sameGuidsameGuid(const(GUID)* (parameter) const(GUID)* aa, const(GUID)* b) nothrow @nogc
{
return memcmp(a, b, GUID.sizeof) == 0;
}
// ---------------------------------------------------------------------------
// Hand-declared COM vtables. Every interface here is { vtbl*, refs } with a
// static vtbl of extern(Windows) function pointers — the raw ABI a framework
// binding has to reproduce. Refcounting is a formality (static lifetime).
extern (Windows) nothrow
{
alias (alias) app.FnQI = _error_FnQI = HRESULT function(void*, const(GUID)*, void**);
alias (alias) app.FnRef = _error_FnRef = ULONG function(void*);
// IDropTarget
alias (alias) app.FnDragEnter = _error_FnDragEnter = HRESULT function(void*, void*, DWORD, (struct) app.POINTLPOINTL, DWORD*);
alias (alias) app.FnDragOver = _error_FnDragOver = HRESULT function(void*, DWORD, (struct) app.POINTLPOINTL, DWORD*);
alias (alias) app.FnDragLeave = _error_FnDragLeave = HRESULT function(void*);
// IDropSource
alias (alias) app.FnQueryContinueDrag = _error_FnQueryContinueDrag = HRESULT function(void*, BOOL, DWORD);
alias (alias) app.FnGiveFeedback = _error_FnGiveFeedback = HRESULT function(void*, DWORD);
// IDataObject
alias (alias) app.FnGetData = _error_FnGetData = HRESULT function(void*, (struct) app.FORMATETCFORMATETC*, (struct) app.STGMEDIUMSTGMEDIUM*);
alias (alias) app.FnQueryGetData = _error_FnQueryGetData = HRESULT function(void*, (struct) app.FORMATETCFORMATETC*);
alias (alias) app.FnGetCanonical = _error_FnGetCanonical = HRESULT function(void*, (struct) app.FORMATETCFORMATETC*, (struct) app.FORMATETCFORMATETC*);
alias (alias) app.FnSetData = _error_FnSetData = HRESULT function(void*, (struct) app.FORMATETCFORMATETC*, (struct) app.STGMEDIUMSTGMEDIUM*, BOOL);
alias (alias) app.FnEnumFormatEtc = _error_FnEnumFormatEtc = HRESULT function(void*, DWORD, void**);
alias (alias) app.FnDAdvise = _error_FnDAdvise = HRESULT function(void*, (struct) app.FORMATETCFORMATETC*, DWORD, void*, DWORD*);
alias (alias) app.FnDUnadvise = _error_FnDUnadvise = HRESULT function(void*, DWORD);
alias (alias) app.FnEnumDAdvise = _error_FnEnumDAdvise = HRESULT function(void*, void**);
// IEnumFORMATETC
alias (alias) app.FnEnumNext = _error_FnEnumNext = HRESULT function(void*, ULONG, (struct) app.FORMATETCFORMATETC*, ULONG*);
alias (alias) app.FnEnumSkip = _error_FnEnumSkip = HRESULT function(void*, ULONG);
alias (alias) app.FnEnumReset = _error_FnEnumReset = HRESULT function(void*);
alias (alias) app.FnEnumClone = _error_FnEnumClone = HRESULT function(void*, void**);
}
struct (struct) app.DropTargetVtblDropTargetVtbl
{
(alias) app.FnQI = _error_FnQI (field) _error_ app.DropTargetVtbl.QueryInterfaceQueryInterface;
(alias) app.FnRef = _error_FnRef (field) _error_ app.DropTargetVtbl.AddRefAddRef, (field) _error_ app.DropTargetVtbl.ReleaseRelease;
(alias) app.FnDragEnter = _error_FnDragEnter (field) _error_ app.DropTargetVtbl.DragEnterDragEnter;
(alias) app.FnDragOver = _error_FnDragOver (field) _error_ app.DropTargetVtbl.DragOverDragOver;
(alias) app.FnDragLeave = _error_FnDragLeave (field) _error_ app.DropTargetVtbl.DragLeaveDragLeave;
(alias) app.FnDragEnter = _error_FnDragEnter (field) _error_ app.DropTargetVtbl.DropDrop; // same signature as DragEnter
}
struct (struct) app.DropSourceVtblDropSourceVtbl
{
(alias) app.FnQI = _error_FnQI (field) _error_ app.DropSourceVtbl.QueryInterfaceQueryInterface;
(alias) app.FnRef = _error_FnRef (field) _error_ app.DropSourceVtbl.AddRefAddRef, (field) _error_ app.DropSourceVtbl.ReleaseRelease;
(alias) app.FnQueryContinueDrag = _error_FnQueryContinueDrag (field) _error_ app.DropSourceVtbl.QueryContinueDragQueryContinueDrag;
(alias) app.FnGiveFeedback = _error_FnGiveFeedback (field) _error_ app.DropSourceVtbl.GiveFeedbackGiveFeedback;
}
struct (struct) app.DataObjectVtblDataObjectVtbl
{
(alias) app.FnQI = _error_FnQI (field) _error_ app.DataObjectVtbl.QueryInterfaceQueryInterface;
(alias) app.FnRef = _error_FnRef (field) _error_ app.DataObjectVtbl.AddRefAddRef, (field) _error_ app.DataObjectVtbl.ReleaseRelease;
(alias) app.FnGetData = _error_FnGetData (field) _error_ app.DataObjectVtbl.GetDataGetData;
(alias) app.FnGetData = _error_FnGetData (field) _error_ app.DataObjectVtbl.GetDataHereGetDataHere;
(alias) app.FnQueryGetData = _error_FnQueryGetData (field) _error_ app.DataObjectVtbl.QueryGetDataQueryGetData;
(alias) app.FnGetCanonical = _error_FnGetCanonical (field) _error_ app.DataObjectVtbl.GetCanonicalFormatEtcGetCanonicalFormatEtc;
(alias) app.FnSetData = _error_FnSetData (field) _error_ app.DataObjectVtbl.SetDataSetData;
(alias) app.FnEnumFormatEtc = _error_FnEnumFormatEtc (field) _error_ app.DataObjectVtbl.EnumFormatEtcEnumFormatEtc;
(alias) app.FnDAdvise = _error_FnDAdvise (field) _error_ app.DataObjectVtbl.DAdviseDAdvise;
(alias) app.FnDUnadvise = _error_FnDUnadvise (field) _error_ app.DataObjectVtbl.DUnadviseDUnadvise;
(alias) app.FnEnumDAdvise = _error_FnEnumDAdvise (field) _error_ app.DataObjectVtbl.EnumDAdviseEnumDAdvise;
}
struct (struct) app.EnumFmtVtblEnumFmtVtbl
{
(alias) app.FnQI = _error_FnQI (field) _error_ app.EnumFmtVtbl.QueryInterfaceQueryInterface;
(alias) app.FnRef = _error_FnRef (field) _error_ app.EnumFmtVtbl.AddRefAddRef, (field) _error_ app.EnumFmtVtbl.ReleaseRelease;
(alias) app.FnEnumNext = _error_FnEnumNext (field) _error_ app.EnumFmtVtbl.NextNext;
(alias) app.FnEnumSkip = _error_FnEnumSkip (field) _error_ app.EnumFmtVtbl.SkipSkip;
(alias) app.FnEnumReset = _error_FnEnumReset (field) _error_ app.EnumFmtVtbl.ResetReset;
(alias) app.FnEnumClone = _error_FnEnumClone (field) _error_ app.EnumFmtVtbl.CloneClone;
}
struct (struct) app.ComObj!(app.DropSourceVtbl)ComObj(Vtbl)
{
(alias) Vtbl = app.DropSourceVtblVtbl* (field) app.DropSourceVtbl* app.ComObj!(app.DropSourceVtbl).vtblvtbl;
LONG (field) _error_ app.ComObj!(app.DropSourceVtbl).refsrefs = 1;
uint (field) uint app.ComObj!(app.DropSourceVtbl).cursorcursor; // EnumFORMATETC position / DropSource call counter
}
// ---------------------------------------------------------------------------
// Demo state.
struct (struct) app.DemoDemo
{
HINSTANCE (field) _error_ app.Demo.instinst;
HWND (field) _error_ app.Demo.hwndhwnd;
uint (field) uint app.Demo.ticksticks;
bool (field) bool app.Demo.autoExitautoExit;
int (field) int app.Demo.phasephase; // index into the schedule, for log readability
long (field) long app.Demo.delayedSetAtdelayedSetAt; // when SetClipboardData(fmt, NULL) was issued
int (field) int app.Demo.renderDemandsrenderDemands; // WM_RENDERFORMAT count
int (field) int app.Demo.renderAllrenderAll; // WM_RENDERALLFORMATS count
bool (field) bool app.Demo.dndDonedndDone;
HANDLE (field) _error_ app.Demo.hDropFileshDropFiles; // CF_HDROP HGLOBAL template (owned by the source)
WCHAR[MAX_PATH] (field) _error_ app.Demo.dropPathdropPath;
ComObj!(struct) app.DropTargetVtblDropTargetVtbl (field) _error_ app.Demo.targettarget;
ComObj!(struct) app.DropSourceVtblDropSourceVtbl (field) _error_ app.Demo.sourcesource;
ComObj!(struct) app.DataObjectVtblDataObjectVtbl (field) _error_ app.Demo.datadata;
}
__gshared (struct) app.DemoDemo _error_ app.gg;
__gshared immutable wchar[] (immutable global) immutable(wstring) app.PAYLOADPAYLOAD = "é漢🎈\0"w; // 4 UTF-16 units + NUL
enum (constant) ulong app.PAYLOAD_BYTES = 10LUPAYLOAD_BYTES = (immutable global) immutable(wstring) app.PAYLOADPAYLOAD.(field) ulong immutable(wstring).lengthlength * 2; // 10
__gshared (struct) app.DropTargetVtblDropTargetVtbl (__gshared global) app.DropTargetVtbl app.gTargetVtblgTargetVtbl;
__gshared (struct) app.DropSourceVtblDropSourceVtbl (__gshared global) app.DropSourceVtbl app.gSourceVtblgSourceVtbl;
__gshared (struct) app.DataObjectVtblDataObjectVtbl (__gshared global) app.DataObjectVtbl app.gDataVtblgDataVtbl;
__gshared (struct) app.EnumFmtVtblEnumFmtVtbl (__gshared global) app.EnumFmtVtbl app.gEnumVtblgEnumVtbl;
// ---------------------------------------------------------------------------
// Crash/hang capture: verdict lines must survive anything OLE does headless.
extern (Windows) LONG app.sehFiltersehFilter(EXCEPTION_POINTERS* ep) nothrow
{
const _error_ codecode = ep && ep.ExceptionRecord ? ep.ExceptionRecord.ExceptionCode : 0;
logEvent("crash seh code=0x%08lx phase=%d", code, g.g.phasephase);
ExitProcess(0);
return 1;
}
extern (Windows) uint uint app.watchdogProc(void* arg) nothrowwatchdogProc(void* (parameter) void* argarg) nothrow
{
Sleep(cast(DWORD) cast((unresolved type) size_tsize_t) arg);
logEvent("watchdog_fired phase=%d dnd_done=%d", g.g.phasephase, g.g.dndDonedndDone ? 1 : 0);
ExitProcess(0);
return 0;
}
// ---------------------------------------------------------------------------
// Clipboard helpers.
const(char)* app.cfNamecfName(UINT (parameter) UINT fmtfmt, char* (parameter) char* scratchscratch, (alias) object.size_t = ulongsize_t (parameter) size_t nn) nothrow
{
switch (fmt)
{
case CF_TEXT:
return "CF_TEXT";
case CF_BITMAP:
return "CF_BITMAP";
case CF_OEMTEXT:
return "CF_OEMTEXT";
case CF_UNICODETEXT:
return "CF_UNICODETEXT";
case CF_LOCALE:
return "CF_LOCALE";
case CF_HDROP:
return "CF_HDROP";
case CF_DIB:
return "CF_DIB";
case CF_DIBV5:
return "CF_DIBV5";
default:
WCHAR[64] _error_ wnamewname;
const _error_ lenlen = GetClipboardFormatNameW(fmt, wname.wname.ptrptr, 64);
if (len > 0)
{
int _error_ pp;
foreach ((parameter) ii; 0 .. len)
if (p < n - 1)
scratch[p++] = wname[i] < 0x80 ? cast(char) wname[i] : '?';
scratch[p] = 0;
}
else
snprintf(scratch, n, "0x%04x", fmt);
return scratch;
}
}
// Log `clip_offer formats=[...]` for the currently-open clipboard.
void void app.logOfferedFormats(const(char)* who) nothrowlogOfferedFormats(const(char)* (parameter) const(char)* whowho) nothrow
{
char[512] (local variable) char[512] lineline = 0;
char[80] (local variable) char[80] scratchscratch;
int (local variable) int pp;
UINT (local variable) _error_ fmtfmt = 0;
int (local variable) int countcount;
while ((fmt = EnumClipboardFormats(fmt)) != 0)
{
const (local variable) const(_error_) nmnm = cfName(fmt, scratch.scratch.ptrptr, scratch.scratch.lengthlength);
(local variable) int pp += int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogcsnprintf((local variable) char[512] lineline.(constant) char* char[512].ptr = &lineptr + (local variable) int pp, (local variable) char[512] lineline.(constant) ulong char[512].length = 512LUlength - (local variable) int pp, "%s%s", (local variable) int countcount ? ",".(constant) immutable(char)* ",".ptr = ","ptr : "".(constant) immutable(char)* "".ptr = ""ptr, nm);
++(local variable) int countcount;
}
logEvent("clip_offer who=%s n=%d formats=[%s] seq=%lu",
who, count, line.line.ptrptr, GetClipboardSequenceNumber());
}
// The GMEM_MOVEABLE contract: SetClipboardData takes an HGLOBAL allocated
// with GMEM_MOVEABLE and the *system* owns it afterwards (never free it,
// never use it again after a successful SetClipboardData).
HANDLE app.makeTextHGlobalmakeTextHGlobal(const(wchar)[] s) nothrow
{
HANDLE _error_ hh = GlobalAlloc(GMEM_MOVEABLE, s.s.lengthlength * 2);
if (h is null)
return null;
void* _error_ pp = GlobalLock(h);
memcpy(p, s.s.ptrptr, s.s.lengthlength * 2);
GlobalUnlock(h);
return h;
}
bool app.openClipboardRetryopenClipboardRetry(HWND owner, int tries = 20) nothrow
{
foreach ((parameter) ii; 0 .. tries)
{
if (OpenClipboard(owner))
return true;
Sleep(50);
}
return false;
}
// Read CF_UNICODETEXT from the open clipboard; log bytes + payload match.
void void app.readUnicodeText(const(char)* who) nothrowreadUnicodeText(const(char)* (parameter) const(char)* whowho) nothrow
{
HANDLE (local variable) _error_ hh = GetClipboardData(CF_UNICODETEXT);
if (h is null)
{
logEvent("clip_read who=%s fmt=CF_UNICODETEXT result=null err=%lu",
who, GetLastError());
return;
}
auto (local variable) _error_ pp = cast(const(wchar)*) GlobalLock(h);
const (local variable) const(_error_) totaltotal = GlobalSize(h); // includes the NUL (and any slack)
(alias) object.size_t = ulongsize_t (local variable) ulong lenlen;
while (p[len])
++(local variable) ulong lenlen;
const (local variable) const(_error_) matchmatch = (local variable) ulong lenlen + 1 == (immutable global) immutable(wstring) app.PAYLOADPAYLOAD.(field) ulong immutable(wstring).lengthlength &&
int core.stdc.string.memcmp(scope const(void*) s1, scope const(void*) s2, ulong n) pure nothrow @nogcmemcmp(p, (immutable global) immutable(wstring) app.PAYLOADPAYLOAD.(field) immutable(wchar)* immutable(wstring).ptrptr, (constant) ulong app.PAYLOAD_BYTES = 10LUPAYLOAD_BYTES) == 0;
logEvent("clip_read who=%s fmt=CF_UNICODETEXT bytes=%zu wchars=%zu payload_match=%d",
who, total, len, match ? 1 : 0);
GlobalUnlock(h);
}
// ---------------------------------------------------------------------------
// Phases 1-2: paste at startup, immediate copy + readback.
void void app.phasePasteStartup() nothrowphasePasteStartup() nothrow
{
logEvent("phase name=paste_startup");
if (!openClipboardRetry(null))
return logEvent("error what=OpenClipboard code=%lu", GetLastError());
void app.logOfferedFormats(const(char)* who) nothrowlogOfferedFormats("owner_startup");
if (IsClipboardFormatAvailable(CF_UNICODETEXT))
void app.readUnicodeText(const(char)* who) nothrowreadUnicodeText("owner_startup");
else
logEvent("clip_read who=owner_startup fmt=CF_UNICODETEXT result=absent");
CloseClipboard();
}
void void app.phaseCopyImmediate() nothrowphaseCopyImmediate() nothrow
{
logEvent("phase name=copy_immediate");
if (!OpenClipboard(g.g.hwndhwnd))
return logEvent("error what=OpenClipboard code=%lu", GetLastError());
EmptyClipboard(); // we become the owner
HANDLE (local variable) _error_ hh = makeTextHGlobal(PAYLOAD);
const (local variable) const(_error_) okok = SetClipboardData(CF_UNICODETEXT, h);
logEvent("clip_send fmt=CF_UNICODETEXT bytes=%zu delayed=0 ok=%d owner=%p",
PAYLOAD_BYTES, ok !is null ? 1 : 0, GetClipboardOwner());
CloseClipboard();
// Read-back: what did Windows synthesize around CF_UNICODETEXT?
if (openClipboardRetry(null))
{
void app.logOfferedFormats(const(char)* who) nothrowlogOfferedFormats("owner_readback");
void app.readUnicodeText(const(char)* who) nothrowreadUnicodeText("owner_readback");
CloseClipboard();
}
}
// ---------------------------------------------------------------------------
// Phase 4: delayed rendering. SetClipboardData(fmt, NULL) promises the data;
// WM_RENDERFORMAT arrives only when somebody calls GetClipboardData.
void void app.phaseCopyDelayed(const(char)* tag) nothrowphaseCopyDelayed(const(char)* (parameter) const(char)* tagtag) nothrow
{
logEvent("phase name=%s", tag);
if (!OpenClipboard(g.g.hwndhwnd))
return logEvent("error what=OpenClipboard code=%lu", GetLastError());
EmptyClipboard();
const (local variable) const(_error_) okok = SetClipboardData(CF_UNICODETEXT, null); // the promise
g.g.delayedSetAtdelayedSetAt = nowUs();
logEvent("clip_send fmt=CF_UNICODETEXT bytes=0 delayed=1 ok=%d", ok is null ? 0 : 1);
// NOTE: SetClipboardData(fmt, NULL) returns NULL on success here — NULL is
// also the "no data" success answer for delayed rendering (check GetLastError).
logEvent("clip_send_delayed err=%lu", GetLastError());
CloseClipboard();
}
// The in-process demand: a thread reads the clipboard 300 ms after the
// delayed SetClipboardData, forcing a WM_RENDERFORMAT into the main pump.
extern (Windows) uint uint app.demandThread(void* arg) nothrowdemandThread(void* (parameter) void* argarg) nothrow
{
Sleep(300);
logEvent("thread=demand action=GetClipboardData t=%lld", nowUs());
if (!openClipboardRetry(null))
return 0;
void app.readUnicodeText(const(char)* who) nothrowreadUnicodeText("demand_thread");
CloseClipboard();
return 0;
}
// The ownership grab: another "app" (a thread opening with no owner window)
// empties the clipboard -> the owner window gets WM_DESTROYCLIPBOARD.
extern (Windows) uint uint app.grabThread(void* arg) nothrowgrabThread(void* (parameter) void* argarg) nothrow
{
if (!openClipboardRetry(null))
return 0;
logEvent("thread=grab action=EmptyClipboard t=%lld", nowUs());
EmptyClipboard();
HANDLE (local variable) _error_ hh = makeTextHGlobal("grabbed\0"w);
SetClipboardData(CF_UNICODETEXT, h);
CloseClipboard();
return 0;
}
void void app.spawnReader(int n) nothrowspawnReader(int (parameter) int nn) nothrow
{
WCHAR[MAX_PATH] (local variable) _error_ exeexe;
GetModuleFileNameW(null, exe.exe.ptrptr, MAX_PATH);
WCHAR[MAX_PATH + 16] (local variable) _error_ cmdcmd;
int (local variable) int pp;
cmd[p++] = '"';
for (int (local variable) int ii = 0; exe[i]; ++i)
cmd[p++] = exe[i];
cmd[p++] = '"';
foreach ((parameter) immutable(wchar) chch; " --reader"w)
cmd[p++] = ch;
cmd[p] = 0;
STARTUPINFOW (local variable) _error_ sisi;
si.cb = STARTUPINFOW.sizeof;
PROCESS_INFORMATION (local variable) _error_ pipi;
logEvent("spawn_reader n=%d", n);
if (!CreateProcessW(null, cmd.cmd.ptrptr, null, null, TRUE, 0, null, null, &si, &pi))
return logEvent("error what=CreateProcessW code=%lu", GetLastError());
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess); // it logs on its own; the pump must keep running
}
// ---------------------------------------------------------------------------
// COM implementations — IDropTarget (the window side).
extern (Windows) HRESULT app.dtQueryInterfacedtQueryInterface(void* self, const(GUID)* riid, void** ppv) nothrow
{
if (sameGuid(riid, &IID_IUnknown) || sameGuid(riid, &IID_IDropTarget))
{
*ppv = self;
return S_OK;
}
*ppv = null;
return E_NOINTERFACE;
}
extern (Windows) ULONG app.comAddRefcomAddRef(void* self) nothrow
{
return cast(ULONG)++(cast(ComObj!(unresolved type) DropTargetVtblDropTargetVtbl*) self).(cast(ComObj!DropTargetVtbl*)self).refsrefs;
}
extern (Windows) ULONG app.comReleasecomRelease(void* self) nothrow
{
return cast(ULONG)--(cast(ComObj!(unresolved type) DropTargetVtblDropTargetVtbl*) self).(cast(ComObj!DropTargetVtbl*)self).refsrefs; // static lifetime
}
// Probe the offered formats through the foreign IDataObject pointer.
void void app.logDataObjectFormats(void* dataObj) nothrowlogDataObjectFormats(void* (parameter) void* dataObjdataObj) nothrow
{
auto (local variable) _error_ objobj = cast(ComObj!(struct) app.DataObjectVtblDataObjectVtbl*) dataObj;
const (local variable) const(_error_) sameObjectsameObject = (parameter) void* dataObjdataObj is &g.(field) _error_ g.datadata;
logEvent("dnd_dataobject ptr=%p is_our_source_object=%d", dataObj, sameObject ? 1 : 0);
(struct) app.FORMATETCFORMATETC (local variable) _error_ fefe = FORMATETC(CF_HDROP, null, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
const (local variable) const(_error_) hrHdrophrHdrop = obj.(field) _error_ obj.vtblvtbl.QueryGetData(dataObj, &fe);
fe.fe.cfFormatcfFormat = CF_UNICODETEXT;
const (local variable) const(_error_) hrTexthrText = obj.(field) _error_ obj.vtblvtbl.QueryGetData(dataObj, &fe);
logEvent("dnd_querygetdata CF_HDROP=0x%08lx CF_UNICODETEXT=0x%08lx", hrHdrop, hrText);
void* (local variable) void* enumPtrenumPtr;
if (obj.(field) _error_ obj.vtblvtbl.EnumFormatEtc(dataObj, DATADIR_GET, &enumPtr) == S_OK && (local variable) void* enumPtrenumPtr)
{
auto (local variable) _error_ enen = cast(ComObj!(struct) app.EnumFmtVtblEnumFmtVtbl*) enumPtr;
char[256] (local variable) char[256] lineline = 0;
char[80] (local variable) char[80] scratchscratch;
int (local variable) int pp, (local variable) int countcount;
(struct) app.FORMATETCFORMATETC (local variable) _error_ gotgot;
ULONG (local variable) _error_ fetchedfetched;
while (en.(field) _error_ en.vtblvtbl.Next(enumPtr, 1, &got, &fetched) == S_OK && fetched == 1)
{
const (local variable) const(_error_) nmnm = cfName(got.got.cfFormatcfFormat, scratch.scratch.ptrptr, scratch.scratch.lengthlength);
(local variable) int pp += int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogcsnprintf((local variable) char[256] lineline.(constant) char* char[256].ptr = &lineptr + (local variable) int pp, (local variable) char[256] lineline.(constant) ulong char[256].length = 256LUlength - (local variable) int pp, "%s%s(tymed=%lu)",
(local variable) int countcount ? ",".(constant) immutable(char)* ",".ptr = ","ptr : "".(constant) immutable(char)* "".ptr = ""ptr, nm, got.(field) _error_ got.tymedtymed);
++(local variable) int countcount;
}
en.(field) _error_ en.vtblvtbl.Release(enumPtr);
logEvent("dnd_enter formats=[%s] n=%d", line.line.ptrptr, count);
}
}
extern (Windows) HRESULT app.dtDragEnterdtDragEnter(void* self, void* (parameter) void* dataObjdataObj, DWORD keys,
(struct) app.POINTLPOINTL (parameter) POINTL ptpt, DWORD* (parameter) DWORD* effecteffect) nothrow
{
logEvent("dnd_enter keys=0x%lx pt=%ld,%ld effects_offered=0x%lx", keys, pt.pt.xx, pt.pt.yy, *effect);
logDataObjectFormats(dataObj);
*effect = DROPEFFECT_COPY; // accept: we want a copy
logEvent("dnd_enter_reply effect=DROPEFFECT_COPY");
return S_OK;
}
extern (Windows) HRESULT app.dtDragOverdtDragOver(void* self, DWORD keys, (struct) app.POINTLPOINTL (parameter) POINTL ptpt, DWORD* (parameter) DWORD* effecteffect) nothrow
{
static __gshared int int overCountoverCount;
if (++overCount <= 3)
logEvent("dnd_over n=%d pt=%ld,%ld effect_in=0x%lx", overCount, pt.pt.xx, pt.pt.yy, *effect);
*effect = DROPEFFECT_COPY;
return S_OK;
}
extern (Windows) HRESULT app.dtDragLeavedtDragLeave(void* self) nothrow
{
logEvent("dnd_leave");
return S_OK;
}
extern (Windows) HRESULT app.dtDropdtDrop(void* self, void* (parameter) void* dataObjdataObj, DWORD keys,
(struct) app.POINTLPOINTL (parameter) POINTL ptpt, DWORD* (parameter) DWORD* effecteffect) nothrow
{
logEvent("dnd_drop pt=%ld,%ld effects_in=0x%lx", pt.pt.xx, pt.pt.yy, *effect);
auto _error_ objobj = cast(ComObj!(unresolved type) DataObjectVtblDataObjectVtbl*) dataObj;
(unresolved type) FORMATETCFORMATETC _error_ fefe = FORMATETC(CF_HDROP, null, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
(unresolved type) STGMEDIUMSTGMEDIUM _error_ medmed;
const _error_ hrhr = obj.obj.vtblvtbl.obj.vtbl.GetDataGetData(dataObj, &fe, &med);
if (hr == S_OK && med.med.tymedtymed == TYMED_HGLOBAL)
{
const _error_ bytesbytes = GlobalSize(med.med.hGlobalhGlobal);
auto _error_ dropdrop = cast((unresolved type) DROPFILESDROPFILES*) GlobalLock(med.med.hGlobalhGlobal);
const _error_ nFilesnFiles = DragQueryFileW(med.med.hGlobalhGlobal, 0xFFFFFFFF, null, 0);
WCHAR[MAX_PATH] _error_ pathpath;
const _error_ lenlen = DragQueryFileW(med.med.hGlobalhGlobal, 0, path.path.ptrptr, MAX_PATH);
char[MAX_PATH] _error_ asciiascii;
int _error_ pp;
foreach ((parameter) ii; 0 .. len)
ascii[p++] = path[i] < 0x80 ? cast(char) path[i] : '?';
ascii[p] = 0;
logEvent("dnd_drop fmt=CF_HDROP bytes=%zu files=%u fwide=%d file0=%s",
bytes, nFiles, drop.drop.fWidefWide, ascii.ascii.ptrptr);
GlobalUnlock(med.med.hGlobalhGlobal);
ReleaseStgMedium(&med);
}
else
logEvent("dnd_drop getdata_hr=0x%08lx tymed=%lu", hr, med.med.tymedtymed);
*effect = DROPEFFECT_COPY;
return S_OK;
}
// ---------------------------------------------------------------------------
// IDropSource — drives the in-process drag to a programmatic drop.
extern (Windows) HRESULT app.dsQueryInterfacedsQueryInterface(void* self, const(GUID)* riid, void** ppv) nothrow
{
if (sameGuid(riid, &IID_IUnknown) || sameGuid(riid, &IID_IDropSource))
{
*ppv = self;
return S_OK;
}
*ppv = null;
return E_NOINTERFACE;
}
extern (Windows) HRESULT app.dsQueryContinueDragdsQueryContinueDrag(void* self, BOOL esc, DWORD keys) nothrow
{
auto _error_ srcsrc = cast(ComObj!(unresolved type) DropSourceVtblDropSourceVtbl*) self;
const _error_ nn = ++src.src.cursorcursor;
if (n <= 3 || n == 6)
logEvent("dnd_source query_continue n=%u esc=%d keys=0x%lx", n, esc, keys);
if (esc)
return DRAGDROP_S_CANCEL;
// No real mouse button is involved: after a few iterations, drop.
return n >= 6 ? DRAGDROP_S_DROP : S_OK;
}
extern (Windows) HRESULT app.dsGiveFeedbackdsGiveFeedback(void* self, DWORD (parameter) DWORD effecteffect) nothrow
{
static __gshared int int nn;
if (++n <= 2)
logEvent("dnd_source give_feedback n=%d effect=0x%lx", n, effect);
return DRAGDROP_S_USEDEFAULTCURSORS;
}
// ---------------------------------------------------------------------------
// IDataObject — offers exactly one format: CF_HDROP as HGLOBAL.
extern (Windows) HRESULT app.doQueryInterfacedoQueryInterface(void* self, const(GUID)* riid, void** ppv) nothrow
{
if (sameGuid(riid, &IID_IUnknown) || sameGuid(riid, &IID_IDataObject))
{
*ppv = self;
return S_OK;
}
*ppv = null;
return E_NOINTERFACE;
}
bool app.isOurFormatisOurFormat(const((struct) app.FORMATETCFORMATETC)* (parameter) const(FORMATETC)* fefe) nothrow
{
return fe.fe.cfFormatcfFormat == CF_HDROP && (fe.fe.tymedtymed & TYMED_HGLOBAL) != 0
&& fe.fe.dwAspectdwAspect == DVASPECT_CONTENT;
}
extern (Windows) HRESULT app.doGetDatadoGetData(void* self, (struct) app.FORMATETCFORMATETC* (parameter) FORMATETC* fefe, (struct) app.STGMEDIUMSTGMEDIUM* (parameter) STGMEDIUM* medmed) nothrow
{
logEvent("dnd_request fmt=0x%04x tymed=%lu aspect=%lu", fe.fe.cfFormatcfFormat, fe.fe.tymedtymed, fe.fe.dwAspectdwAspect);
if (!isOurFormat(fe))
return DV_E_FORMATETC;
// Hand out a fresh copy; the callee owns and frees it (pUnkForRelease=null).
const _error_ sizesize = GlobalSize(g.g.hDropFileshDropFiles);
HANDLE _error_ dupdup = GlobalAlloc(GMEM_MOVEABLE, size);
memcpy(GlobalLock(dup), GlobalLock(g.g.hDropFileshDropFiles), size);
GlobalUnlock(dup);
GlobalUnlock(g.g.hDropFileshDropFiles);
med.med.tymedtymed = TYMED_HGLOBAL;
med.med.hGlobalhGlobal = dup;
med.med.pUnkForReleasepUnkForRelease = null;
logEvent("dnd_send fmt=CF_HDROP bytes=%zu", size);
return S_OK;
}
extern (Windows) HRESULT app.doGetDataHeredoGetDataHere(void* self, (struct) app.FORMATETCFORMATETC* (parameter) FORMATETC* fefe, (struct) app.STGMEDIUMSTGMEDIUM* (parameter) STGMEDIUM* medmed) nothrow
{
return E_NOTIMPL;
}
extern (Windows) HRESULT app.doQueryGetDatadoQueryGetData(void* self, (struct) app.FORMATETCFORMATETC* (parameter) FORMATETC* fefe) nothrow
{
return isOurFormat(fe) ? S_OK : DV_E_FORMATETC;
}
extern (Windows) HRESULT app.doGetCanonicaldoGetCanonical(void* self, (struct) app.FORMATETCFORMATETC* (parameter) FORMATETC* aa, (struct) app.FORMATETCFORMATETC* b) nothrow
{
*b = *a;
b.b.ptdptd = null;
return E_NOTIMPL;
}
extern (Windows) HRESULT app.doSetDatadoSetData(void* self, (struct) app.FORMATETCFORMATETC* (parameter) FORMATETC* fefe, (struct) app.STGMEDIUMSTGMEDIUM* (parameter) STGMEDIUM* medmed, BOOL rel) nothrow
{
return E_NOTIMPL;
}
extern (Windows) HRESULT app.doEnumFormatEtcdoEnumFormatEtc(void* self, DWORD dir, void** ppEnum) nothrow
{
if (dir != DATADIR_GET)
{
*ppEnum = null;
return E_NOTIMPL;
}
__gshared ComObj!(unresolved type) EnumFmtVtblEnumFmtVtbl _error_ enen; // single static enumerator (demo-grade)
en.en.vtblvtbl = &gEnumVtbl;
en.en.refsrefs = 1;
en.en.cursorcursor = 0;
*ppEnum = &en;
return S_OK;
}
extern (Windows) HRESULT app.doDAdvisedoDAdvise(void* self, (struct) app.FORMATETCFORMATETC* (parameter) FORMATETC* fefe, DWORD (parameter) DWORD ff, void* sink, DWORD* conn) nothrow
{
return OLE_E_ADVISENOTSUPPORTED;
}
extern (Windows) HRESULT app.doDUnadvisedoDUnadvise(void* self, DWORD conn) nothrow
{
return OLE_E_ADVISENOTSUPPORTED;
}
extern (Windows) HRESULT app.doEnumDAdvisedoEnumDAdvise(void* self, void** ppEnum) nothrow
{
*ppEnum = null;
return OLE_E_ADVISENOTSUPPORTED;
}
// IEnumFORMATETC over the one-format list.
extern (Windows) HRESULT app.efQueryInterfaceefQueryInterface(void* self, const(GUID)* riid, void** ppv) nothrow
{
if (sameGuid(riid, &IID_IUnknown) || sameGuid(riid, &IID_IEnumFORMATETC))
{
*ppv = self;
return S_OK;
}
*ppv = null;
return E_NOINTERFACE;
}
extern (Windows) HRESULT app.efNextefNext(void* self, ULONG celt, (struct) app.FORMATETCFORMATETC* rgelt, ULONG* (parameter) ULONG* fetchedfetched) nothrow
{
auto _error_ enen = cast(ComObj!(unresolved type) EnumFmtVtblEnumFmtVtbl*) self;
ULONG _error_ gotgot = 0;
if (celt >= 1 && en.en.cursorcursor == 0)
{
rgelt[0] = FORMATETC(CF_HDROP, null, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
en.en.cursorcursor = 1;
got = 1;
}
if (fetched)
*fetched = got;
return got == celt ? S_OK : S_FALSE;
}
extern (Windows) HRESULT app.efSkipefSkip(void* self, ULONG celt) nothrow
{
auto _error_ enen = cast(ComObj!(unresolved type) EnumFmtVtblEnumFmtVtbl*) self;
en.en.cursorcursor += celt;
return en.en.cursorcursor <= 1 ? S_OK : S_FALSE;
}
extern (Windows) HRESULT app.efResetefReset(void* self) nothrow
{
(cast(ComObj!(unresolved type) EnumFmtVtblEnumFmtVtbl*) self).(cast(ComObj!EnumFmtVtbl*)self).cursorcursor = 0;
return S_OK;
}
extern (Windows) HRESULT app.efCloneefClone(void* self, void** ppv) nothrow
{
*ppv = null;
return E_NOTIMPL;
}
void void app.initVtbls() nothrowinitVtbls() nothrow
{
(__gshared global) app.DropTargetVtbl app.gTargetVtblgTargetVtbl = DropTargetVtbl(&dtQueryInterface, &comAddRef, &comRelease,
&dtDragEnter, &dtDragOver, &dtDragLeave, &dtDrop);
(__gshared global) app.DropSourceVtbl app.gSourceVtblgSourceVtbl = DropSourceVtbl(&dsQueryInterface, &comAddRef, &comRelease,
&dsQueryContinueDrag, &dsGiveFeedback);
(__gshared global) app.DataObjectVtbl app.gDataVtblgDataVtbl = DataObjectVtbl(&doQueryInterface, &comAddRef, &comRelease,
&doGetData, &doGetDataHere, &doQueryGetData, &doGetCanonical,
&doSetData, &doEnumFormatEtc, &doDAdvise, &doDUnadvise, &doEnumDAdvise);
(__gshared global) app.EnumFmtVtbl app.gEnumVtblgEnumVtbl = EnumFmtVtbl(&efQueryInterface, &comAddRef, &comRelease,
&efNext, &efSkip, &efReset, &efClone);
g.(field) _error_ g.targettarget.g.target.vtblvtbl = &gTargetVtbl;
g.(field) _error_ g.sourcesource.g.source.vtblvtbl = &gSourceVtbl;
g.(field) _error_ g.datadata.g.data.vtblvtbl = &gDataVtbl;
}
// ---------------------------------------------------------------------------
// Phase 7: the in-process drag — DoDragDrop onto our own registered target.
// DoDragDrop's modal loop is driven by input events; headless there are none,
// so a helper thread jiggles the (wineserver-virtual) cursor to keep the loop
// calling QueryContinueDrag.
extern (Windows) uint uint app.jiggleThread(void* arg) nothrowjiggleThread(void* (parameter) void* argarg) nothrow
{
POINT (local variable) _error_ pp;
GetCursorPos(&p);
foreach ((local variable) int ii; 0 .. 150)
{
SetCursorPos(p.p.xx + (i & 1), p.p.yy);
Sleep(20);
if (g.(field) _error_ g.dndDonedndDone)
break;
}
return 0;
}
void void app.buildDropFiles() nothrowbuildDropFiles() nothrow
{
// A real witness file in the prefix's temp dir.
WCHAR[MAX_PATH] (local variable) _error_ tmptmp;
GetTempPathW(MAX_PATH, tmp.tmp.ptrptr);
int (local variable) int pp;
while (tmp[p])
++(local variable) int pp;
foreach ((parameter) ulong ii, (parameter) immutable(wchar) chch; "wsi-f16-drop.txt\0"w)
tmp[p + i] = ch;
HANDLE (local variable) _error_ ff = CreateFileW(tmp.tmp.ptrptr, GENERIC_WRITE, 0, null, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, null);
DWORD (local variable) _error_ wrotewrote;
WriteFile(f, "wsi f16 drop payload\n"."wsi f16 drop payload\n".ptrptr, 21, &wrote, null);
CloseHandle(f);
g.(field) _error_ g.dropPathdropPath[] = tmp[];
int (local variable) int pathLenpathLen;
while (tmp[pathLen])
++(local variable) int pathLenpathLen;
const (local variable) const(_error_) bytesbytes = DROPFILES.sizeof + ((local variable) int pathLenpathLen + 2) * 2; // path + double NUL
g.g.hDropFileshDropFiles = GlobalAlloc(GMEM_MOVEABLE, bytes);
auto (local variable) _error_ dropdrop = cast((struct) app.DROPFILESDROPFILES*) GlobalLock(g.g.hDropFileshDropFiles);
*drop = DROPFILES.init;
drop.drop.pFilespFiles = DROPFILES.sizeof;
drop.drop.fWidefWide = TRUE;
auto (local variable) _error_ dstdst = cast(wchar*)(cast(ubyte*) drop + DROPFILES.sizeof);
void* core.stdc.string.memcpy(return scope void* s1, scope const(void*) s2, ulong n) pure nothrow @nogcmemcpy(dst, tmp.(field) _error_ tmp.ptrptr, (local variable) int pathLenpathLen * 2);
dst[pathLen] = 0;
dst[pathLen + 1] = 0; // the list terminator
GlobalUnlock(g.g.hDropFileshDropFiles);
logEvent("dnd_payload file_created bytes=%zu", bytes);
}
void void app.phaseDnd() nothrowphaseDnd() nothrow
{
logEvent("phase name=dnd");
void app.buildDropFiles() nothrowbuildDropFiles();
// Park the virtual cursor over our client area so the OLE loop's
// WindowFromPoint hit-test finds the registered IDropTarget.
RECT (local variable) _error_ rcrc;
GetWindowRect(g.g.hwndhwnd, &rc);
SetCursorPos((rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2);
logEvent("dnd_source cursor_parked=%ld,%ld", (rc.left + rc.right) / 2,
(rc.top + rc.bottom) / 2);
HANDLE (local variable) _error_ jigjig = CreateThread(null, 0, &jiggleThread, null, 0, null);
DWORD (local variable) _error_ effecteffect = DROPEFFECT_NONE;
logEvent("dnd_source DoDragDrop_begin ok_effects=COPY|MOVE|LINK");
const (local variable) const(_error_) hrhr = DoDragDrop(&g.g.datadata, &g.g.sourcesource, // hand-rolled COM objects
DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK, &effect);
g.g.dndDonedndDone = true;
const(char)* (local variable) const(char)* hrNamehrName = hr == DRAGDROP_S_DROP ? "DRAGDROP_S_DROP"
: hr == DRAGDROP_S_CANCEL ? "DRAGDROP_S_CANCEL" : "other";
logEvent("dnd_source DoDragDrop_returned hr=0x%08lx (%s) effect=0x%lx query_continue_calls=%u",
hr, hrName, effect, g.g.sourcesource.g.source.cursorcursor);
WaitForSingleObject(jig, 4000);
CloseHandle(jig);
}
// ---------------------------------------------------------------------------
// WndProc: the clipboard-protocol messages are the finding.
extern (Windows) LRESULT app.wndProcwndProc(HWND (parameter) HWND hwndhwnd, UINT (parameter) UINT msgmsg, WPARAM wp, LPARAM lp) nothrow
{
switch (msg)
{
case WM_RENDERFORMAT:
// Demanded NOW: the clipboard is already open for us — just SetClipboardData.
++g.g.renderDemandsrenderDemands;
logEvent("clip_request msg=WM_RENDERFORMAT fmt=0x%04llx demand_n=%d us_since_delayed_set=%lld",
cast(ulong) wp, g.g.renderDemandsrenderDemands, nowUs() - g.g.delayedSetAtdelayedSetAt);
if (wp == CF_UNICODETEXT)
{
SetClipboardData(CF_UNICODETEXT, makeTextHGlobal(PAYLOAD));
logEvent("clip_send fmt=CF_UNICODETEXT bytes=%zu delayed_render=1", PAYLOAD_BYTES);
}
return 0;
case WM_RENDERALLFORMATS:
// Owner is dying with promises outstanding: render everything, with
// the open/owner-check ceremony the docs require.
++g.g.renderAllrenderAll;
logEvent("clip_request msg=WM_RENDERALLFORMATS");
if (OpenClipboard(hwnd))
{
if (GetClipboardOwner() is hwnd)
{
SetClipboardData(CF_UNICODETEXT, makeTextHGlobal(PAYLOAD));
logEvent("clip_send fmt=CF_UNICODETEXT bytes=%zu render_all=1", PAYLOAD_BYTES);
}
CloseClipboard();
}
return 0;
case WM_DESTROYCLIPBOARD:
logEvent("ownership_lost msg=WM_DESTROYCLIPBOARD seq=%lu", GetClipboardSequenceNumber());
return 0;
case WM_CLIPBOARDUPDATE:
logEvent("clip_update msg=WM_CLIPBOARDUPDATE seq=%lu owner=%p",
GetClipboardSequenceNumber(), GetClipboardOwner());
return 0;
case WM_TIMER:
++g.g.ticksticks;
if (!g.g.autoExitautoExit)
return 0;
switch (g.g.ticksticks)
{
case 3:
g.g.phasephase = 1;
phasePasteStartup();
break;
case 12:
g.g.phasephase = 2;
phaseCopyImmediate();
break;
case 25:
g.g.phasephase = 3;
spawnReader(1);
break;
case 60:
g.g.phasephase = 4;
phaseCopyDelayed("copy_delayed_thread_demand");
CloseHandle(CreateThread(null, 0, &demandThread, null, 0, null));
break;
case 90:
g.g.phasephase = 5;
phaseCopyDelayed("copy_delayed_process_demand");
spawnReader(2);
break;
case 130:
g.g.phasephase = 6;
logEvent("phase name=grab_ownership");
CloseHandle(CreateThread(null, 0, &grabThread, null, 0, null));
break;
case 150:
g.g.phasephase = 7;
phaseDnd();
break;
case 190:
g.g.phasephase = 8;
phaseCopyDelayed("copy_delayed_then_destroy");
DestroyWindow(hwnd); // -> WM_RENDERALLFORMATS before WM_DESTROY
break;
default:
break;
}
return 0;
case WM_CHAR: // interactive mode: c=copy v=paste d=dnd
if (wp == 'c')
phaseCopyImmediate();
else if (wp == 'v')
phasePasteStartup();
else if (wp == 'd')
phaseDnd();
return 0;
case WM_PAINT:
PAINTSTRUCT _error_ psps;
HDC _error_ dcdc = BeginPaint(hwnd, &ps);
RECT _error_ rcrc;
GetClientRect(hwnd, &rc);
FillRect(dc, &rc, cast(HBRUSH)(COLOR_WINDOW + 1));
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
logEvent("msg name=WM_DESTROY render_demands=%d render_all=%d",
g.g.renderDemandsrenderDemands, g.g.renderAllrenderAll);
KillTimer(hwnd, 1);
RevokeDragDrop(hwnd);
PostQuitMessage(0);
return 0;
default:
return DefWindowProcW(hwnd, msg, wp, lp);
}
}
// ---------------------------------------------------------------------------
// --reader role: a separate process in the same prefix reading the clipboard.
int int app.readerMain() nothrowreaderMain() nothrow
{
instrumentInit("f16_win32_reader");
logEvent("reader_start pid=%lu", GetCurrentProcessId());
if (!openClipboardRetry(null))
{
logEvent("error what=OpenClipboard code=%lu", GetLastError());
return 0;
}
void app.logOfferedFormats(const(char)* who) nothrowlogOfferedFormats("reader");
if (IsClipboardFormatAvailable(CF_UNICODETEXT))
void app.readUnicodeText(const(char)* who) nothrowreadUnicodeText("reader"); // a delayed format triggers WM_RENDERFORMAT in the owner
else
logEvent("clip_read who=reader fmt=CF_UNICODETEXT result=absent");
CloseClipboard();
logEvent("reader_exit code=0");
return 0;
}
// ---------------------------------------------------------------------------
bool bool app.wantAutoExit() nothrowwantAutoExit() nothrow
{
WCHAR[8] (local variable) _error_ bufbuf;
const (local variable) const(_error_) nn = GetEnvironmentVariableW("WSI_AUTO_EXIT"w."WSI_AUTO_EXIT"w.ptrptr, buf.buf.ptrptr, buf.buf.lengthlength);
return n >= 1 && n < buf.(field) _error_ buf.lengthlength && buf[0] == '1';
}
int int D main(string[] args)main((alias) object.string = stringstring[] (parameter) string[] argsargs)
{
foreach ((parameter) string aa; (parameter) string[] argsargs[1 .. $])
if ((local variable) string aa == "--reader")
return int app.readerMain() nothrowreaderMain();
instrumentInit("f16_win32");
logEvent("init_start");
g.g.autoExitautoExit = wantAutoExit();
g.g.instinst = GetModuleHandleW(null);
SetUnhandledExceptionFilter(cast(LPTOP_LEVEL_EXCEPTION_FILTER)&sehFilter);
if (g.(field) _error_ g.autoExitautoExit)
CloseHandle(CreateThread(null, 0, &watchdogProc,
cast(void*) cast((unresolved type) size_tsize_t) 30000, 0, null));
// OLE drag-and-drop REQUIRES OleInitialize (it layers the clipboard/DnD
// machinery on top of CoInitialize's STA); a CoInitialize-only thread gets
// E_OUTOFMEMORY-flavored failures from RegisterDragDrop.
const (local variable) const(_error_) hrOlehrOle = OleInitialize(null);
logEvent("step name=OleInitialize hr=0x%08lx", hrOle);
void app.initVtbls() nothrowinitVtbls();
WNDCLASSEXW (local variable) _error_ wcwc;
wc.cbSize = WNDCLASSEXW.sizeof;
wc.lpfnWndProc = &wndProc;
wc.hInstance = g.g.instinst;
wc.lpszClassName = "wsi-f16-class"w."wsi-f16-class"w.ptrptr;
wc.hCursor = LoadCursorW(null, IDC_ARROW);
wc.hbrBackground = cast(HBRUSH)(COLOR_WINDOW + 1);
if (!RegisterClassExW(&wc))
logEvent("error what=RegisterClassExW code=%lu", GetLastError());
g.g.hwndhwnd = CreateWindowExW(0, "wsi-f16-class"w."wsi-f16-class"w.ptrptr, "wsi-f16-clipboard-dnd"w."wsi-f16-clipboard-dnd"w.ptrptr,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 480, 320,
null, null, g.g.instinst, null);
if (g.(field) _error_ g.hwndhwnd is null)
{
logEvent("error what=CreateWindowExW code=%lu", GetLastError());
return 0;
}
ShowWindow(g.g.hwndhwnd, SW_SHOW);
UpdateWindow(g.g.hwndhwnd);
logEvent("window_created hwnd=%p", g.g.hwndhwnd);
const (local variable) const(_error_) lstlst = AddClipboardFormatListener(g.g.hwndhwnd);
logEvent("step name=AddClipboardFormatListener ret=%d err=%lu", lst, GetLastError());
const (local variable) const(_error_) hrReghrReg = RegisterDragDrop(g.g.hwndhwnd, &g.g.targettarget);
logEvent("step name=RegisterDragDrop hr=0x%08lx", hrReg);
SetTimer(g.g.hwndhwnd, 1, 16, null);
MSG (local variable) _error_ msgmsg;
while (GetMessageW(&msg, null, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
void app.OleUninitialize() nothrow @nogcOleUninitialize();
logEvent("exit code=0 render_demands=%d render_all=%d", g.g.renderDemandsrenderDemands, g.g.renderAllrenderAll);
return 0;
}