app.dhover×465 error×63all
// F16 X11 demo — clipboard + file drag-and-drop, both implemented by hand on
// the one mechanism X11 has for either: ICCCM selections
// (../../../features/f16-clipboard-dnd.md; findings in ../../f16-clipboard-dnd.md).
//
// Modes (--mode=..., default `dnd`):
//   copy       own CLIPBOARD (and PRIMARY — the one-line contrast), serve
//              TARGETS/TIMESTAMP/UTF8_STRING ('é漢🎈') to any requestor
//              (xclip -o); log every SelectionRequest; exit on SelectionClear
//   copy-incr  same with a 400 KiB payload -> INCR *sending* (the
//              property-delete-driven chunk choreography, logged per chunk)
//   paste      log the owner's TARGETS, then convert UTF8_STRING; a payload
//              above the owner's chunk limit arrives as INCR *receive*
//   dnd        XDND v5, BOTH SIDES in one process: this window is the target
//              (XdndAware); a second Display connection plays the source —
//              owns XdndSelection, Enter/Position, aborts once with Leave,
//              re-enters, Drops, serves text/uri-list through a normal
//              selection transfer, gets XdndFinished. (The CI default.)
//
// Headless-safe: no X server -> prints `SKIP:` and exits 0. WSI_AUTO_EXIT=1
// tightens the deadline so CI runs stay bounded. run.sh drives xclip passes.
module 
(module) app
app
;
import
(module) c
c
; // ImportC: Xlib + Xutil + Xatom + poll
C preprocess command `cpp` failed for file `/home/runner/work/sparkles/sparkles/docs/research/window-system-integration/os-apis/x11/examples/f16-clipboard-dnd/c.c`, exit status 1
import
(module) instrument
instrument
;
import
(package) core
core
.
(package) core.stdc
stdc
.
(module) core.stdc.config

D compatible types that correspond to various basic types in associated C and C++ compilers.

Source

core/stdc/config.d

@copyrightCopyright Sean Kelly 2005 - 2009.@licenseDistributed under the Boost Software License 1.0. (See accompanying file LICENSE)@authorsSean Kelly@standardsISO/IEC 9899:1999 (E)
config
: c_long, c_ulong;
import
(package) core
core
.
(package) core.stdc
stdc
.
(module) core.stdc.stdio

D header file for C99 <stdio.h>

pubs.opengroup.org/onlinepubs/009695399/basedefs/stdio.h.html, stdio.h

Source

core/stdc/stdio.d

@copyrightCopyright Sean Kelly 2005 - 2009.@licenseDistributed under the Boost Software License 1.0. (See accompanying file LICENSE)@authorsSean Kelly, Alex Rønne Petersen@standardsISO/IEC 9899:1999 (E)
stdio
:
(alias) app.printf = int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
,
(alias) app.snprintf = int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogc
snprintf
;
import
(package) core
core
.
(package) core.stdc
stdc
.
(module) core.stdc.stdlib

D header file for C99.

pubs.opengroup.org/onlinepubs/009695399/basedefs/stdlib.h.html, stdlib.h

Source

core/stdc/stdlib.d

@copyrightCopyright Sean Kelly 2005 - 2014.@licenseDistributed under the Boost Software License 1.0. (See accompanying file LICENSE)@authorsSean Kelly@standardsISO/IEC 9899:1999 (E)
stdlib
:
(alias) app.getenv = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogc
getenv
,
(alias) app.malloc = void* core.stdc.stdlib.malloc(ulong size) nothrow @nogc
malloc
;
import
(package) core
core
.
(package) core.stdc
stdc
.
(module) core.stdc.string

D header file for C99.

pubs.opengroup.org/onlinepubs/009695399/basedefs/string.h.html, string.h

Source

core/stdc/string.d

@copyrightCopyright Sean Kelly 2005 - 2009.@licenseDistributed under the Boost Software License 1.0. (See accompanying file LICENSE)@authorsSean Kelly@standardsISO/IEC 9899:1999 (E)
string
:
(alias) app.memcpy = void* core.stdc.string.memcpy(return scope void* s1, scope const(void*) s2, ulong n) pure nothrow @nogc
memcpy
,
(alias) app.strcmp = int core.stdc.string.strcmp(scope const(char*) s1, scope const(char*) s2) pure nothrow @nogc
strcmp
,
(alias) app.strlen = ulong core.stdc.string.strlen(scope const(char*) s) pure nothrow @nogc
strlen
;
// -- macro constants ImportC cannot export (cast-expression #defines) -------- enum : c_long {
(enum value) app.PropertyChangeMask = 4194304L
PropertyChangeMask
= 1L << 22,
(enum value) app.StructureNotifyMask = 131072L
StructureNotifyMask
= 1L << 17,
} enum // XEvent.type discriminators {
(enum value) app.PropertyNotify = 28
PropertyNotify
= 28,
(enum value) app.SelectionClear = 29
SelectionClear
= 29,
(enum value) app.SelectionRequest = 30
SelectionRequest
= 30,
(enum value) app.SelectionNotify = 31
SelectionNotify
= 31,
(enum value) app.ClientMessage = 33
ClientMessage
= 33,
} enum {
(enum value) app.PropertyNewValue = 0
PropertyNewValue
= 0,
(enum value) app.PropertyDelete = 1
PropertyDelete
= 1 }
enum {
(enum value) app.PropModeReplace = 0
PropModeReplace
= 0,
(enum value) app.PropModeAppend = 2
PropModeAppend
= 2 }
enum {
(enum value) app.None = 0
None
= 0,
(enum value) app.False = 0
False
= 0,
(enum value) app.True = 1
True
= 1,
(enum value) app.POLLIN = 1
POLLIN
= 0x001 }
enum Atom
(constant) _error_ app.XA_PRIMARY = __error
XA_PRIMARY
= 1,
(constant) _error_ app.XA_ATOM = __error
XA_ATOM
= 4,
(constant) _error_ app.XA_INTEGER = __error
XA_INTEGER
= 19,
(constant) _error_ app.XA_STRING = __error
XA_STRING
= 31;
undefined identifier `Atom`
undefined identifier `Atom`
undefined identifier `Atom`
undefined identifier `Atom`
enum
(alias) object.size_t = ulong
size_t
(constant) ulong app.INCR_THRESHOLD = 262144LU
INCR_THRESHOLD
= 256 * 1024; // own a bigger payload -> send INCR
enum
(alias) object.size_t = ulong
size_t
(constant) ulong app.INCR_CHUNK = 65536LU
INCR_CHUNK
= 64 * 1024; // bytes per INCR append
enum int
(constant) int app.XDND_VERSION = 5
XDND_VERSION
= 5;
// -- small helpers ------------------------------------------------------------ /// Atom -> name for log lines (rotating static buffers so one emitf can hold 4). const(char)*
app.an

Atom -> name for log lines (rotating static buffers so one emitf can hold 4).

an
(Display*
(parameter) Display* dpy
dpy
, Atom
(parameter) Atom a
a
) @nogc nothrow
undefined identifier `Display`
undefined identifier `Atom`
{ static char[64][4]
char[64][4] bufs
bufs
;
static int
int next
next
;
if (a == None) return "None"; auto
_error_ buf
buf
= bufs[next & 3].
bufs[next & 3].ptr
ptr
;
next++; char*
_error_ s
s
= XGetAtomName(dpy, a);
snprintf(buf, 64, "%s", s); XFree(s); return buf; } /// ICCCM-correct ownership timestamp: "A zero-length append to a property is /// a way to obtain a timestamp for this purpose; the timestamp is in the /// corresponding PropertyNotify event." (ICCCM § 2.1) — never CurrentTime. Time
app.serverTimestamp

ICCCM-correct ownership timestamp: "A zero-length append to a property is a way to obtain a timestamp for this purpose; the timestamp is in the corresponding PropertyNotify event." (ICCCM § 2.1) — never CurrentTime.

serverTimestamp
(Display*
(parameter) Display* dpy
dpy
, Window
(parameter) Window win
win
) @nogc nothrow
undefined identifier `Time`, did you mean enum member `True`?
undefined identifier `Display`
undefined identifier `Window`
{ const
_error_ prop
prop
= XInternAtom(dpy, "WSI_TIMESTAMP", False);
XChangeProperty(dpy, win, prop, XA_STRING, 8, PropModeAppend, null, 0); for (;;) { XEvent
_error_ ev
ev
;
XNextEvent(dpy, &ev); if (ev.
ev.type
type
== PropertyNotify && ev.xproperty.atom == prop)
return ev.xproperty.time; } } /// Read a whole property (deleting it when `del`); returns malloc'd bytes. ubyte*
app.readProperty

Read a whole property (deleting it when del); returns malloc'd bytes.

readProperty
(Display*
(parameter) Display* dpy
dpy
, Window
(parameter) Window win
win
, Atom
(parameter) Atom prop
prop
, int del,
undefined identifier `Display`
undefined identifier `Window`
undefined identifier `Atom`
out Atom
(parameter) Atom type
type
, out int
(parameter) int format
format
, out c_ulong
(parameter) c_ulong nitems
nitems
) @nogc nothrow
undefined identifier `Atom`
{ Atom
_error_ actualType
actualType
;
int
_error_ actualFormat
actualFormat
;
c_ulong
_error_ n
n
,
_error_ after
after
;
ubyte*
_error_ data
data
;
XGetWindowProperty(dpy, win, prop, 0, 0x1FFFFFFF, del, AnyPropertyType, &actualType, &actualFormat, &n, &after, &data); type = actualType; format = actualFormat; nitems = n; return data; // caller XFree()s } enum Atom
(constant) _error_ app.AnyPropertyType = __error
AnyPropertyType
= 0;
undefined identifier `Atom`
// -- selection OWNER side ----------------------------------------------------- // One struct serves both the clipboard owner and the XDND source: on X11 the // clipboard and DnD literally share the transfer machinery (the F16 finding). struct
(struct) app.SelOwner
SelOwner
{ Display*
(field) _error_ app.SelOwner.dpy
dpy
;
undefined identifier `Display`
Window
(field) _error_ app.SelOwner.win
win
;
undefined identifier `Window`
Atom
(field) _error_ app.SelOwner.dataTarget
dataTarget
; // UTF8_STRING (clipboard) or text/uri-list (XDND)
undefined identifier `Atom`
const(ubyte)*
(field) const(ubyte)* app.SelOwner.data
data
;
(alias) object.size_t = ulong
size_t
(field) ulong app.SelOwner.len
len
;
Time
(field) _error_ app.SelOwner.ts
ts
;
undefined identifier `Time`, did you mean enum member `True`?
Atom
(field) _error_ app.SelOwner.targetsAtom
targetsAtom
,
(field) _error_ app.SelOwner.timestampAtom
timestampAtom
,
(field) _error_ app.SelOwner.incrAtom
incrAtom
;
undefined identifier `Atom`
undefined identifier `Atom`
undefined identifier `Atom`
// active INCR send (at most one in this demo) Window
(field) _error_ app.SelOwner.incrRequestor
incrRequestor
;
undefined identifier `Window`
Atom
(field) _error_ app.SelOwner.incrProperty
incrProperty
;
undefined identifier `Atom`
(alias) object.size_t = ulong
size_t
(field) ulong app.SelOwner.incrOffset
incrOffset
;
int
(field) int app.SelOwner.incrChunks
incrChunks
;
bool
(field) bool app.SelOwner.incrActive
incrActive
;
} /// Answer one SelectionRequest per ICCCM § 2.2: convert into the named /// property on the requestor's window, then confirm with SelectionNotify /// (property = None refuses). Payloads above INCR_THRESHOLD start an INCR /// transfer (ICCCM § 2.7.2) instead of one giant ChangeProperty. void
app.handleSelectionRequest

Answer one SelectionRequest per ICCCM § 2.2: convert into the named property on the requestor's window, then confirm with SelectionNotify (property = None refuses). Payloads above INCR_THRESHOLD start an INCR transfer (ICCCM § 2.7.2) instead of one giant ChangeProperty.

handleSelectionRequest
(ref
(struct) app.SelOwner
SelOwner
o, ref XSelectionRequestEvent req) @nogc nothrow
undefined identifier `XSelectionRequestEvent`
{ emitf("clip_request", "requestor=0x%lx selection=%s target=%s property=%s", req.requestor, an(o.
o.dpy
dpy
, req.
req.selection
selection
), an(o.
o.dpy
dpy
, req.target),
an(o.
o.dpy
dpy
, req.
req.property
property
));
// "If the specified property is None, the requestor is an obsolete // client. Owners are encouraged to support these clients by using the // specified target atom as the property name" (ICCCM § 2.2). Atom
_error_ property
property
= req.
req.property
property
== None ? req.target : req.
req.property
property
;
if (req.target == o.
o.targetsAtom
targetsAtom
)
{ Atom[3]
_error_ targets
targets
= [o.
o.targetsAtom
targetsAtom
, o.
o.timestampAtom
timestampAtom
, o.
o.dataTarget
dataTarget
];
XChangeProperty(o.
o.dpy
dpy
, req.requestor, property, XA_ATOM, 32,
PropModeReplace, cast(ubyte*) targets.
targets.ptr
ptr
, 3);
} else if (req.target == o.
o.timestampAtom
timestampAtom
)
{ c_long
_error_ t
t
= cast(c_long) o.
o.ts
ts
;
XChangeProperty(o.
o.dpy
dpy
, req.requestor, property, XA_INTEGER, 32,
PropModeReplace, cast(ubyte*)&t, 1); } else if (req.target == o.
o.dataTarget
dataTarget
&& o.
o.len
len
> INCR_THRESHOLD)
{ // INCR start: property of type INCR holding "a lower bound on the // number of bytes of data in the selection" (ICCCM § 2.7.2); we then // watch the requestor's window for the property *deletions* that // drive the chunk loop. Input masks are per-client, so selecting // PropertyChangeMask on a foreign window is legal. c_long
_error_ bound
bound
= cast(c_long) o.
o.len
len
;
XSelectInput(o.
o.dpy
dpy
, req.requestor, PropertyChangeMask);
XChangeProperty(o.
o.dpy
dpy
, req.requestor, property, o.
o.incrAtom
incrAtom
, 32,
PropModeReplace, cast(ubyte*)&bound, 1); o.
o.incrRequestor
incrRequestor
= req.requestor;
o.
o.incrProperty
incrProperty
= property;
o.
o.incrOffset
incrOffset
= 0;
o.
o.incrChunks
incrChunks
= 0;
o.
o.incrActive
incrActive
= true;
emitf("clip_send", "bytes=%zu incr=1 phase=start lower_bound=%ld", o.
o.len
len
, bound);
} else if (req.target == o.
o.dataTarget
dataTarget
)
{ XChangeProperty(o.
o.dpy
dpy
, req.requestor, property, o.
o.dataTarget
dataTarget
, 8,
PropModeReplace, o.
o.data
data
, cast(int) o.
o.len
len
);
emitf("clip_send", "bytes=%zu incr=0 target=%s", o.
o.len
len
, an(o.
o.dpy
dpy
, o.
o.dataTarget
dataTarget
));
} else { property = None; // refuse: SelectionNotify with property None emitf("clip_send", "refused target=%s", an(o.
o.dpy
dpy
, req.target));
} XEvent
_error_ reply
reply
;
reply.xselection.
reply.xselection.type
type
= SelectionNotify;
reply.xselection.display = req.display; reply.xselection.requestor = req.requestor; reply.xselection.
reply.xselection.selection
selection
= req.
req.selection
selection
;
reply.xselection.target = req.target; reply.xselection.
reply.xselection.property
property
= property;
reply.xselection.time = req.time; XSendEvent(o.
o.dpy
dpy
, req.requestor, False, 0, &reply);
XFlush(o.
o.dpy
dpy
);
} /// INCR chunk pump: each PropertyDelete from the requestor means "read, /// send the next chunk"; a zero-length append terminates. Returns true when /// the transfer just completed. bool
app.handleIncrDelete

INCR chunk pump: each PropertyDelete from the requestor means "read, send the next chunk"; a zero-length append terminates. Returns true when the transfer just completed.

handleIncrDelete
(ref
(struct) app.SelOwner
SelOwner
o, ref XPropertyEvent pev) @nogc nothrow
undefined identifier `XPropertyEvent`
{ if (!o.
o.incrActive
incrActive
|| pev.window != o.
o.incrRequestor
incrRequestor
|| pev.atom != o.
o.incrProperty
incrProperty
|| pev.state != PropertyDelete)
return false; const
_error_ remaining
remaining
= o.
o.len
len
- o.
o.incrOffset
incrOffset
;
const
_error_ n
n
= remaining < INCR_CHUNK ? remaining : INCR_CHUNK;
XChangeProperty(o.
o.dpy
dpy
, o.
o.incrRequestor
incrRequestor
, o.
o.incrProperty
incrProperty
, o.
o.dataTarget
dataTarget
, 8,
PropModeReplace, o.
o.data
data
+ o.
o.incrOffset
incrOffset
, cast(int) n);
XFlush(o.
o.dpy
dpy
);
if (n == 0) // final zero-length chunk -> done { o.
o.incrActive
incrActive
= false;
emitf("clip_send", "bytes=%zu incr=1 phase=done chunks=%d", o.
o.len
len
, o.
o.incrChunks
incrChunks
);
return true; } o.
o.incrOffset
incrOffset
+= n;
o.
o.incrChunks
incrChunks
++;
emitf("incr_chunk", "n=%d bytes=%zu offset=%zu", o.
o.incrChunks
incrChunks
, n, o.
o.incrOffset
incrOffset
);
return false; } // -- selection REQUESTOR side --------------------------------------------------- /// Fetch one conversion result (the property a SelectionNotify named), /// following the INCR receive loop when the type is INCR: delete the INCR /// property to start, then GetProperty(delete=True) on each PropertyNewValue /// until a zero-length chunk (ICCCM § 2.7.2). Returns total bytes.
(alias) object.size_t = ulong
size_t
app.fetchConverted

Fetch one conversion result (the property a SelectionNotify named), following the INCR receive loop when the type is INCR: delete the INCR property to start, then GetProperty(delete=True) on each PropertyNewValue until a zero-length chunk (ICCCM § 2.7.2). Returns total bytes.

fetchConverted
(Display*
(parameter) Display* dpy
dpy
, Window
(parameter) Window win
win
, Atom
(parameter) Atom prop
prop
, Atom
(parameter) Atom incrAtom
incrAtom
,
undefined identifier `Display`
undefined identifier `Window`
undefined identifier `Atom`
undefined identifier `Atom`
ubyte* outBuf,
(alias) object.size_t = ulong
size_t
outCap, out bool
(parameter) bool wasIncr
wasIncr
, out int
(parameter) int chunks
chunks
) @nogc nothrow
{ Atom
_error_ type
type
;
int
_error_ format
format
;
c_ulong
_error_ nitems
nitems
;
ubyte*
_error_ data
data
= readProperty(dpy, win, prop, True, type, format, nitems);
if (type != incrAtom) { wasIncr = false; chunks = 0; const
_error_ n
n
= cast(
(unresolved type) size_t
size_t
) nitems;
if (data !is null && outBuf !is null && n <= outCap) memcpy(outBuf, data, n); if (data !is null) XFree(data); return n; } // INCR: the GetProperty(delete=True) above performed the ICCCM "starts // the transfer process by deleting the (type==INCR) property". const
_error_ bound
bound
= data !is null && nitems >= 1 ? *cast(c_long*) data : 0;
if (data !is null) XFree(data); emitf("paste_incr", "phase=start lower_bound=%ld", bound); wasIncr = true; chunks = 0;
(unresolved type) size_t
size_t
_error_ total
total
= 0;
for (;;) { XEvent
_error_ ev
ev
;
XNextEvent(dpy, &ev); if (ev.
ev.type
type
!= PropertyNotify || ev.xproperty.atom != prop
|| ev.xproperty.state != PropertyNewValue) continue; ubyte*
_error_ chunk
chunk
= readProperty(dpy, win, prop, True, type, format, nitems);
const
_error_ n
n
= cast(
(unresolved type) size_t
size_t
) nitems;
if (chunk !is null) { if (outBuf !is null && total + n <= outCap) memcpy(outBuf + total, chunk, n); XFree(chunk); } if (n == 0) break; // zero-length chunk terminates chunks++; total += n; emitf("incr_chunk", "n=%d bytes=%zu total=%zu", chunks, n, total); } return total; } // -- modes ---------------------------------------------------------------------- int
app.runCopy
runCopy
(Display*
(parameter) Display* dpy
dpy
, Window
(parameter) Window win
win
, bool big, long
(parameter) long deadlineUs
deadlineUs
) @nogc nothrow
undefined identifier `Display`
undefined identifier `Window`
{
(unresolved type) SelOwner
SelOwner
_error_ owner
owner
;
owner.
owner.dpy
dpy
= dpy;
owner.
owner.win
win
= win;
owner.
owner.targetsAtom
targetsAtom
= XInternAtom(dpy, "TARGETS", False);
owner.
owner.timestampAtom
timestampAtom
= XInternAtom(dpy, "TIMESTAMP", False);
owner.
owner.incrAtom
incrAtom
= XInternAtom(dpy, "INCR", False);
owner.
owner.dataTarget
dataTarget
= XInternAtom(dpy, "UTF8_STRING", False);
const
_error_ clipboard
clipboard
= XInternAtom(dpy, "CLIPBOARD", False);
static immutable
(unresolved type) string
string
string small
small
= "é漢🎈"; // 2+3+4 UTF-8 bytes
if (big) { enum
(unresolved type) size_t
size_t
(constant) size_t bigLen = 400 * 1024
bigLen
= 400 * 1024; // > INCR_THRESHOLD -> INCR send
auto
_error_ p
p
= cast(ubyte*) malloc(bigLen);
foreach (
(parameter) i
i
; 0 .. bigLen)
p[i] = cast(ubyte)('A' + i % 26); owner.
owner.data
data
= p;
owner.
owner.len
len
= bigLen;
} else { owner.
owner.data
data
= cast(const ubyte*) small.
small.ptr
ptr
;
owner.
owner.len
len
= small.
small.length
length
;
} owner.
owner.ts
ts
= serverTimestamp(dpy, win);
XSetSelectionOwner(dpy, clipboard, win, owner.
owner.ts
ts
);
const
_error_ got
got
= XGetSelectionOwner(dpy, clipboard) == win;
emitf("clip_offer", "selection=CLIPBOARD targets=[TARGETS,TIMESTAMP,UTF8_STRING] bytes=%zu ts=%lu owned=%d", owner.
owner.len
len
, owner.
owner.ts
ts
, cast(int) got);
if (!big) { // The PRIMARY contrast: identical machinery, one more line. XSetSelectionOwner(dpy, XA_PRIMARY, win, owner.
owner.ts
ts
);
emit("clip_offer selection=PRIMARY targets=[TARGETS,TIMESTAMP,UTF8_STRING] note=same_machinery_one_line"); } const
_error_ fd
fd
= XConnectionNumber(dpy);
bool
_error_ incrDone
incrDone
= false;
while (nowUs() < deadlineUs) { while (XPending(dpy) > 0) { XEvent
_error_ ev
ev
;
XNextEvent(dpy, &ev); switch (ev.
ev.type
type
)
{ case SelectionRequest: handleSelectionRequest(owner, ev.xselectionrequest); break; case PropertyNotify: if (handleIncrDelete(owner, ev.xproperty)) incrDone = true; break; case SelectionClear: emitf("ownership_lost", "selection=%s ts=%lu", an(dpy, ev.xselectionclear.
ev.xselectionclear.selection
selection
), ev.xselectionclear.time);
if (ev.xselectionclear.
ev.xselectionclear.selection
selection
== clipboard)
return 0; // the F16 "another app copies" requirement break; default: break; } } if (incrDone && big) return 0; // copy-incr: one full INCR transfer served is the demo pollfd
_error_ pfd
pfd
;
pfd.
pfd.fd
fd
= fd;
pfd.events = POLLIN; poll(&pfd, 1, 100); } emit("auto_exit reason=deadline"); return 0; } int
app.runPaste
runPaste
(Display*
(parameter) Display* dpy
dpy
, Window
(parameter) Window win
win
) @nogc nothrow
undefined identifier `Display`
undefined identifier `Window`
{ const
_error_ clipboard
clipboard
= XInternAtom(dpy, "CLIPBOARD", False);
const
_error_ targetsAtom
targetsAtom
= XInternAtom(dpy, "TARGETS", False);
const
_error_ utf8
utf8
= XInternAtom(dpy, "UTF8_STRING", False);
const
_error_ incrAtom
incrAtom
= XInternAtom(dpy, "INCR", False);
const
_error_ dest
dest
= XInternAtom(dpy, "WSI_PASTE", False);
const
_error_ ts
ts
= serverTimestamp(dpy, win);
const
_error_ owner
owner
= XGetSelectionOwner(dpy, clipboard);
emitf("step", "name=XGetSelectionOwner selection=CLIPBOARD owner=0x%lx", owner); if (owner == None) { printf("SKIP: nobody owns CLIPBOARD (run via run.sh with xclip)\n"); return 0; } // 1) what does the owner offer? (TARGETS first, per ICCCM) XConvertSelection(dpy, clipboard, targetsAtom, dest, win, ts); for (;;) { XEvent
_error_ ev
ev
;
XNextEvent(dpy, &ev); if (ev.
ev.type
type
!= SelectionNotify)
continue; if (ev.xselection.
ev.xselection.property
property
== None)
{ emit("clip_offer targets=[] note=owner_refused_TARGETS"); break; } Atom
_error_ type
type
;
int
_error_ format
format
;
c_ulong
_error_ n
n
;
auto
_error_ data
data
= readProperty(dpy, win, dest, True, type, format, n);
char[512]
_error_ list
list
;
(unresolved type) size_t
size_t
_error_ pos
pos
= 0;
foreach (
(parameter) i
i
; 0 .. cast(
(unresolved type) size_t
size_t
) n)
{ const
_error_ a
a
= (cast(Atom*) data)[i];
pos += snprintf(list.
list.ptr
ptr
+ pos, list.
list.length
length
- pos, "%s%s",
i ? ",".
",".ptr
ptr
: "".
"".ptr
ptr
, an(dpy, a));
if (pos >= list.
list.length
length
- 64)
break; } XFree(data); emitf("clip_offer", "targets=[%s] count=%lu", list.
list.ptr
ptr
, n);
break; } // 2) convert the text itself (INCR receive when the owner chunks it) XConvertSelection(dpy, clipboard, utf8, dest, win, ts); for (;;) { XEvent
_error_ ev
ev
;
XNextEvent(dpy, &ev); if (ev.
ev.type
type
!= SelectionNotify)
continue; if (ev.xselection.
ev.xselection.property
property
== None)
{ emit("paste_data refused=1"); return 0; } static __gshared ubyte[4 * 1024 * 1024]
ubyte[4 * 1024 * 1024] buf
buf
;
bool
_error_ wasIncr
wasIncr
;
int
_error_ chunks
chunks
;
const
_error_ total
total
= fetchConverted(dpy, win, dest, incrAtom, buf.
buf.ptr
ptr
,
buf.
buf.length
length
, wasIncr, chunks);
emitf("paste_data", "fmt=UTF8_STRING bytes=%zu incr=%d chunks=%d", total, cast(int) wasIncr, chunks); if (total <= 64) emitf("paste_text", "text=\"%.*s\"", cast(int) total, cast(char*) buf.
buf.ptr
ptr
);
return 0; } } // -- XDND: both sides in one process --------------------------------------------- // Target = `win` on `dpy`; source = a second connection (`sdpy`) with its own // 1x1 unmapped window that owns XdndSelection. Atoms are server-global, so the // same ids work on both connections; window ids likewise. struct
(struct) app.DndAtoms
DndAtoms
{ Atom
(field) _error_ app.DndAtoms.aware
aware
,
(field) _error_ app.DndAtoms.selection
selection
,
(field) _error_ app.DndAtoms.enter
enter
,
(field) _error_ app.DndAtoms.position
position
,
(field) _error_ app.DndAtoms.status
status
,
(field) _error_ app.DndAtoms.leave
leave
,
(field) _error_ app.DndAtoms.drop
drop
,
(field) _error_ app.DndAtoms.finished
finished
,
undefined identifier `Atom`
undefined identifier `Atom`
undefined identifier `Atom`
undefined identifier `Atom`
undefined identifier `Atom`
undefined identifier `Atom`
undefined identifier `Atom`
undefined identifier `Atom`
undefined identifier `Atom`
undefined identifier `Atom`
undefined identifier `Atom`
(field) _error_ app.DndAtoms.actionCopy
actionCopy
,
(field) _error_ app.DndAtoms.typeList
typeList
,
(field) _error_ app.DndAtoms.uriList
uriList
;
}
(struct) app.DndAtoms
DndAtoms
app.internDnd
internDnd
(Display*
(parameter) Display* dpy
dpy
) @nogc nothrow
undefined identifier `Display`
{
(unresolved type) DndAtoms
DndAtoms
_error_ a
a
;
a.
a.aware
aware
= XInternAtom(dpy, "XdndAware", False);
a.
a.selection
selection
= XInternAtom(dpy, "XdndSelection", False);
a.
a.enter
enter
= XInternAtom(dpy, "XdndEnter", False);
a.
a.position
position
= XInternAtom(dpy, "XdndPosition", False);
a.
a.status
status
= XInternAtom(dpy, "XdndStatus", False);
a.
a.leave
leave
= XInternAtom(dpy, "XdndLeave", False);
a.
a.drop
drop
= XInternAtom(dpy, "XdndDrop", False);
a.
a.finished
finished
= XInternAtom(dpy, "XdndFinished", False);
a.
a.actionCopy
actionCopy
= XInternAtom(dpy, "XdndActionCopy", False);
a.
a.typeList
typeList
= XInternAtom(dpy, "XdndTypeList", False);
a.
a.uriList
uriList
= XInternAtom(dpy, "text/uri-list", False);
return a; } void
app.sendDndMessage
sendDndMessage
(Display*
(parameter) Display* dpy
dpy
, Window
(parameter) Window dest
dest
, Atom
(parameter) Atom type
type
, Window from,
undefined identifier `Display`
undefined identifier `Window`
undefined identifier `Atom`
undefined identifier `Window`
c_long l1, c_long l2, c_long l3, c_long l4) @nogc nothrow { XEvent
_error_ ev
ev
;
ev.xclient.
ev.xclient.type
type
= ClientMessage;
ev.xclient.window = dest; ev.xclient.message_type = type; ev.xclient.
ev.xclient.format
format
= 32;
ev.xclient.
ev.xclient.data
data
.l[0] = cast(c_long) from;
ev.xclient.
ev.xclient.data
data
.l[1] = l1;
ev.xclient.
ev.xclient.data
data
.l[2] = l2;
ev.xclient.
ev.xclient.data
data
.l[3] = l3;
ev.xclient.
ev.xclient.data
data
.l[4] = l4;
XSendEvent(dpy, dest, False, 0, &ev); XFlush(dpy); } int
app.runDnd
runDnd
(Display*
(parameter) Display* dpy
dpy
, Window
(parameter) Window win
win
, long
(parameter) long deadlineUs
deadlineUs
) @nogc nothrow
undefined identifier `Display`
undefined identifier `Window`
{ const
_error_ da
da
= internDnd(dpy);
// -- target side: announce XdndAware (a window property, not an event mask) c_long
_error_ ver
ver
= XDND_VERSION;
XChangeProperty(dpy, win, da.
da.aware
aware
, XA_ATOM, 32, PropModeReplace,
cast(ubyte*)&ver, 1); XSync(dpy, False); // flush: the source connection reads this property next emitf("step", "name=XdndAware_set version=%ld window=0x%lx", ver, win); // -- source side: a second connection, as a separate client would be Display*
_error_ sdpy
sdpy
= XOpenDisplay(null);
const
_error_ sroot
sroot
= XRootWindow(sdpy, XDefaultScreen(sdpy));
Window
_error_ swin
swin
= XCreateSimpleWindow(sdpy, sroot, 0, 0, 1, 1, 0, 0, 0);
emitf("step", "name=source_connection fd=%d window=0x%lx", XConnectionNumber(sdpy), swin); XSelectInput(sdpy, swin, PropertyChangeMask); // for serverTimestamp // Source checks the target's XdndAware before any message (spec step 1). { Atom
_error_ type
type
;
int
_error_ format
format
;
c_ulong
_error_ n
n
;
auto
_error_ p
p
= readProperty(sdpy, win, da.
da.aware
aware
, False, type, format, n);
emitf("dnd_source", "target_XdndAware=%ld", n >= 1 ? *cast(c_long*) p : -1); if (p !is null) XFree(p); } // Source owns XdndSelection and advertises 4 types -> the >3-types path: // XdndEnter carries only 3 atoms; bit 0 of l[1] says "look at XdndTypeList".
(unresolved type) SelOwner
SelOwner
_error_ src
src
;
src.
src.dpy
dpy
= sdpy;
src.
src.win
win
= swin;
src.
src.targetsAtom
targetsAtom
= XInternAtom(sdpy, "TARGETS", False);
src.
src.timestampAtom
timestampAtom
= XInternAtom(sdpy, "TIMESTAMP", False);
src.
src.incrAtom
incrAtom
= XInternAtom(sdpy, "INCR", False);
src.
src.dataTarget
dataTarget
= da.
da.uriList
uriList
;
static immutable
(unresolved type) string
string
string uri
uri
= "file:///tmp/wsi-f16-dropped.txt\r\n";
src.
src.data
data
= cast(const ubyte*) uri.
uri.ptr
ptr
;
src.
src.len
len
= uri.
uri.length
length
;
src.
src.ts
ts
= serverTimestamp(sdpy, swin);
XSetSelectionOwner(sdpy, da.
da.selection
selection
, swin, src.
src.ts
ts
);
Atom[4]
_error_ types
types
= [da.
da.uriList
uriList
, XInternAtom(sdpy, "UTF8_STRING", False),
XInternAtom(sdpy, "text/plain", False), XInternAtom(sdpy, "TEXT", False)]; XChangeProperty(sdpy, swin, da.
da.typeList
typeList
, XA_ATOM, 32, PropModeReplace,
cast(ubyte*) types.
types.ptr
ptr
, 4);
emitf("dnd_source", "owns=XdndSelection ts=%lu types=4 type_list_set=1", src.
src.ts
ts
);
void
void sendEnterAndPosition(int x, int y) nothrow @nogc
sendEnterAndPosition
(int
(parameter) int x
x
, int
(parameter) int y
y
) @nogc nothrow
{ sendDndMessage(sdpy, win, da.
da.enter
enter
, swin,
(cast(c_long) XDND_VERSION << 24) | 1 /* more-than-3-types bit */ , cast(c_long) types[0], cast(c_long) types[1], cast(c_long) types[2]); sendDndMessage(sdpy, win, da.
da.position
position
, swin, 0,
(x << 16) | y, cast(c_long) src.
src.ts
ts
, cast(c_long) da.
da.actionCopy
actionCopy
);
emitf("dnd_source", "send=XdndEnter,XdndPosition version=5 more_types=1 pos=%d,%d", x, y); } sendEnterAndPosition(100, 100); // -- the negotiation, multiplexing both connections' fds ------------------- pollfd[2]
_error_ pfds
pfds
;
pfds[0].
pfds[0].fd
fd
= XConnectionNumber(dpy);
pfds[1].
pfds[1].fd
fd
= XConnectionNumber(sdpy);
pfds[0].events = pfds[1].events = POLLIN; int
_error_ statusCount
statusCount
= 0;
bool
_error_ leftOnce
leftOnce
= false,
_error_ finished
finished
= false;
Time
_error_ dropTime
dropTime
= 0;
while (!finished && nowUs() < deadlineUs) { // ---- target events (dpy) ---- while (XPending(dpy) > 0) { XEvent
_error_ ev
ev
;
XNextEvent(dpy, &ev); if (ev.
ev.type
type
== ClientMessage && ev.xclient.message_type == da.
da.enter
enter
)
{ const
_error_ srcWin
srcWin
= cast(Window) ev.xclient.
ev.xclient.data
data
.l[0];
const
_error_ moreTypes
moreTypes
= (ev.xclient.
ev.xclient.data
data
.l[1] & 1) != 0;
emitf("dnd_enter", "source=0x%lx version=%ld formats=[%s,%s,%s] more_types=%d", srcWin, ev.xclient.
ev.xclient.data
data
.l[1] >> 24,
an(dpy, cast(Atom) ev.xclient.
ev.xclient.data
data
.l[2]),
an(dpy, cast(Atom) ev.xclient.
ev.xclient.data
data
.l[3]),
an(dpy, cast(Atom) ev.xclient.
ev.xclient.data
data
.l[4]), cast(int) moreTypes);
if (moreTypes) // the full list lives on the source window { Atom
_error_ type
type
;
int
_error_ format
format
;
c_ulong
_error_ n
n
;
auto
_error_ p
p
= readProperty(dpy, srcWin, da.
da.typeList
typeList
, False, type, format, n);
char[256]
_error_ list
list
;
(unresolved type) size_t
size_t
_error_ pos
pos
= 0;
foreach (
(parameter) i
i
; 0 .. cast(
(unresolved type) size_t
size_t
) n)
pos += snprintf(list.
list.ptr
ptr
+ pos, list.
list.length
length
- pos, "%s%s",
i ? ",".
",".ptr
ptr
: "".
"".ptr
ptr
, an(dpy, (cast(Atom*) p)[i]));
XFree(p); emitf("dnd_enter", "XdndTypeList=[%s] count=%lu", list.
list.ptr
ptr
, n);
} } else if (ev.
ev.type
type
== ClientMessage && ev.xclient.message_type == da.
da.position
position
)
{ const
_error_ pos
pos
= ev.xclient.
ev.xclient.data
data
.l[2];
emitf("dnd_position", "source=0x%lx pos=%ld,%ld action=%s", ev.xclient.
ev.xclient.data
data
.l[0], pos >> 16, pos & 0xffff,
an(dpy, cast(Atom) ev.xclient.
ev.xclient.data
data
.l[4]));
// XdndStatus: bit0 accept; empty rect (l[2]=l[3]=0) = keep sending sendDndMessage(dpy, cast(Window) ev.xclient.
ev.xclient.data
data
.l[0], da.
da.status
status
,
win, 1, 0, 0, cast(c_long) da.
da.actionCopy
actionCopy
);
emit("dnd_status send accept=1 action=XdndActionCopy"); } else if (ev.
ev.type
type
== ClientMessage && ev.xclient.message_type == da.
da.leave
leave
)
emit("dnd_leave source_aborted=1 cached_formats_dropped=1"); else if (ev.
ev.type
type
== ClientMessage && ev.xclient.message_type == da.
da.drop
drop
)
{ dropTime = cast(Time) ev.xclient.
ev.xclient.data
data
.l[2];
emitf("dnd_drop", "source=0x%lx time=%lu", ev.xclient.
ev.xclient.data
data
.l[0], dropTime);
// data transfer = a perfectly ordinary selection conversion XConvertSelection(dpy, da.
da.selection
selection
, da.
da.uriList
uriList
, da.
da.selection
selection
,
win, dropTime); emit("step name=XConvertSelection selection=XdndSelection target=text/uri-list"); } else if (ev.
ev.type
type
== SelectionNotify)
{ static __gshared ubyte[4096]
ubyte[4096] dbuf
dbuf
;
bool
_error_ wasIncr
wasIncr
;
int
_error_ chunks
chunks
;
const
_error_ total
total
= fetchConverted(dpy, win, ev.xselection.
ev.xselection.property
property
,
src.
src.incrAtom
incrAtom
, dbuf.
dbuf.ptr
ptr
, dbuf.
dbuf.length
length
, wasIncr, chunks);
emitf("dnd_data", "fmt=text/uri-list bytes=%zu uri=\"%.*s\"", total, cast(int)(total >= 2 ? total - 2 : total), cast(char*) dbuf.
dbuf.ptr
ptr
);
sendDndMessage(dpy, swin, da.
da.finished
finished
, win, 1 /* accepted */ ,
cast(c_long) da.
da.actionCopy
actionCopy
, 0, 0);
emit("dnd_finished send success=1 action=XdndActionCopy"); } } // ---- source events (sdpy) ---- while (XPending(sdpy) > 0) { XEvent
_error_ ev
ev
;
XNextEvent(sdpy, &ev); if (ev.
ev.type
type
== ClientMessage && ev.xclient.message_type == da.
da.status
status
)
{ statusCount++; emitf("dnd_source", "recv=XdndStatus accept=%ld action=%s n=%d", ev.xclient.
ev.xclient.data
data
.l[1] & 1,
an(sdpy, cast(Atom) ev.xclient.
ev.xclient.data
data
.l[4]), statusCount);
if (!leftOnce) { // exercise the abort path once: Leave, then re-enter leftOnce = true; sendDndMessage(sdpy, win, da.
da.leave
leave
, swin, 0, 0, 0, 0);
emit("dnd_source send=XdndLeave (abort path)"); sendEnterAndPosition(120, 80); } else { sendDndMessage(sdpy, win, da.
da.drop
drop
, swin, 0,
cast(c_long) src.
src.ts
ts
, 0, 0);
emit("dnd_source send=XdndDrop"); } } else if (ev.
ev.type
type
== SelectionRequest)
handleSelectionRequest(src, ev.xselectionrequest); // same code as clipboard else if (ev.
ev.type
type
== ClientMessage && ev.xclient.message_type == da.
da.finished
finished
)
{ emitf("dnd_source", "recv=XdndFinished success=%ld action=%s", ev.xclient.
ev.xclient.data
data
.l[1] & 1, an(sdpy, cast(Atom) ev.xclient.
ev.xclient.data
data
.l[2]));
finished = true; } } if (!finished) poll(pfds.
pfds.ptr
ptr
, 2, 100);
} XDestroyWindow(sdpy, swin); XCloseDisplay(sdpy); emitf("teardown", "dnd_complete=%d", cast(int) finished); return finished ? 0 : 1; } // --------------------------------------------------------------------------- int
int D main(string[] args)
main
(
(alias) object.string = string
string
[]
(parameter) string[] args
args
)
{
void instrument.initInstrument(const(char)* demoName) nothrow @nogc

Names the demo, starts the monotonic clock, and emits init_start. Call this first, before any platform API call.

initInstrument
("f16_x11");
const(char)*
(local variable) const(char)* mode
mode
= "dnd";
foreach (
(parameter) string a
a
;
(parameter) string[] args
args
[1 .. $])
if (
(local variable) string a
a
.
(field) ulong string.length
length
> 7 &&
(local variable) string a
a
[0 .. 7] == "--mode=")
(local variable) const(char)* mode
mode
= (
(local variable) string a
a
[7 .. $] ~ '\0').
(field) immutable(char)* string.ptr
ptr
;
const
(local variable) const(char*) envAuto
envAuto
=
char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogc
getenv
("WSI_AUTO_EXIT");
const
(local variable) const(bool) autoExit
autoExit
=
(local variable) const(char*) envAuto
envAuto
!is null &&
(local variable) const(char*) envAuto
envAuto
[0] == '1';
const
(local variable) const(int) deadlineUs
deadlineUs
=
(local variable) const(bool) autoExit
autoExit
? 8_000_000 : 30_000_000;
Display*
(local variable) _error_ dpy
dpy
= XOpenDisplay(null);
undefined identifier `Display`
undefined identifier `XOpenDisplay`
if (dpy is null) {
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
("SKIP: no X11 display (XOpenDisplay returned null)\n");
return 0; }
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogc

Emit an event with a printf-formatted key=value ... payload.

emitf
("step", "name=XOpenDisplay fd=%d mode=%s", XConnectionNumber(dpy),
(local variable) const(char)* mode
mode
);
undefined identifier `XConnectionNumber`
const
(local variable) const(_error_) screen
screen
= XDefaultScreen(dpy);
undefined identifier `XDefaultScreen`
Window
(local variable) _error_ win
win
= XCreateSimpleWindow(dpy, XRootWindow(dpy, screen), 0, 0,
undefined identifier `Window`
undefined identifier `XCreateSimpleWindow`
320, 200, 1, XBlackPixel(dpy, screen), XWhitePixel(dpy, screen)); XStoreName(dpy, win, "Sparkles · F16 clipboard+dnd");
undefined identifier `XStoreName`
XSelectInput(dpy, win, StructureNotifyMask | PropertyChangeMask);
undefined identifier `XSelectInput`
XMapWindow(dpy, win);
undefined identifier `XMapWindow`
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogc

Emit an event with a printf-formatted key=value ... payload.

emitf
("window_created", "xid=0x%lx size=320x200", win);
int
(local variable) int rc
rc
;
if (
int core.stdc.string.strcmp(scope const(char*) s1, scope const(char*) s2) pure nothrow @nogc
strcmp
(
(local variable) const(char)* mode
mode
, "copy") == 0)
(local variable) int rc
rc
= runCopy(dpy, win, false, deadlineUs);
else if (
int core.stdc.string.strcmp(scope const(char*) s1, scope const(char*) s2) pure nothrow @nogc
strcmp
(
(local variable) const(char)* mode
mode
, "copy-incr") == 0)
(local variable) int rc
rc
= runCopy(dpy, win, true, deadlineUs);
else if (
int core.stdc.string.strcmp(scope const(char*) s1, scope const(char*) s2) pure nothrow @nogc
strcmp
(
(local variable) const(char)* mode
mode
, "paste") == 0)
(local variable) int rc
rc
= runPaste(dpy, win);
else
(local variable) int rc
rc
= runDnd(dpy, win, deadlineUs);
XDestroyWindow(dpy, win);
undefined identifier `XDestroyWindow`
XCloseDisplay(dpy);
undefined identifier `XCloseDisplay`
return
(local variable) int rc
rc
;
}