1 /**
2  * X11 protocol.
3  * Work in progress.
4  *
5  * License:
6  *   This Source Code Form is subject to the terms of
7  *   the Mozilla Public License, v. 2.0. If a copy of
8  *   the MPL was not distributed with this file, You
9  *   can obtain one at http://mozilla.org/MPL/2.0/.
10  *
11  * Authors:
12  *   Vladimir Panteleev <ae@cy.md>
13  */
14 
15 module ae.net.x11;
16 
17 import std.algorithm.comparison : min;
18 import std.algorithm.searching;
19 import std.ascii : toLower;
20 import std.conv : to;
21 import std.exception;
22 import std.meta;
23 import std.process : environment;
24 import std.socket;
25 import std.traits : hasIndirections, ReturnType;
26 import std.typecons : Nullable;
27 
28 public import deimos.X11.X;
29 public import deimos.X11.Xmd;
30 import deimos.X11.Xproto;
31 public import deimos.X11.Xprotostr;
32 
33 import ae.net.asockets;
34 import ae.utils.array;
35 import ae.utils.exception : CaughtException;
36 import ae.utils.meta;
37 import ae.utils.promise;
38 
39 debug(X11) import std.stdio : stderr;
40 
41 /// These are always 32-bit in the protocol,
42 /// but are defined as possibly 64-bit in X.h.
43 /// Redefine these in terms of the protocol we implement here.
44 public
45 {
46     alias CARD32    Window;
47     alias CARD32    Drawable;
48     alias CARD32    Font;
49     alias CARD32    Pixmap;
50     alias CARD32    Cursor;
51     alias CARD32    Colormap;
52     alias CARD32    GContext;
53     alias CARD32    Atom;
54     alias CARD32    VisualID;
55     alias CARD32    Time;
56     alias CARD8     KeyCode;
57     alias CARD32    KeySym;
58 }
59 
60 /// Used for CreateWindow and ChangeWindowAttributes.
61 struct WindowAttributes
62 {
63 	/// This will generate `Nullable` fields `backPixmap`, `backPixel`, ...
64 	mixin Optionals!(
65 		CWBackPixmap      , Pixmap               ,
66 		CWBackPixel       , CARD32               ,
67 		CWBorderPixmap    , Pixmap               ,
68 		CWBorderPixel     , CARD32               ,
69 		CWBitGravity      , typeof(ForgetGravity),
70 		CWWinGravity      , typeof(UnmapGravity) ,
71 		CWBackingStore    , typeof(NotUseful)    ,
72 		CWBackingPlanes   , CARD32               ,
73 		CWBackingPixel    , CARD32               ,
74 		CWOverrideRedirect, BOOL                 ,
75 		CWSaveUnder       , BOOL                 ,
76 		CWEventMask       , typeof(NoEventMask)  ,
77 		CWDontPropagate   , typeof(NoEventMask)  ,
78 		CWColormap        , Colormap             ,
79 		CWCursor          , Cursor               ,
80 	);
81 }
82 
83 /// Used for ConfigureWindow.
84 struct WindowConfiguration
85 {
86 	/// This will generate `Nullable` fields `x`, `y`, ...
87 	mixin Optionals!(
88 		CWX           , INT16,
89 		CWY           , INT16,
90 		CWWidth       , CARD16,
91 		CWHeight      , CARD16,
92 		CWBorderWidth , CARD16,
93 		CWSibling     , Window,
94 		CWStackMode   , typeof(Above),
95 	);
96 }
97 
98 /// Used for CreateGC, ChangeGC and CopyGC.
99 struct GCAttributes
100 {
101 	/// This will generate `Nullable` fields `c_function`, `planeMask`, ...
102 	mixin Optionals!(
103 		GCFunction          , typeof(GXclear)       ,
104 		GCPlaneMask         , CARD32                ,
105 		GCForeground        , CARD32                ,
106 		GCBackground        , CARD32                ,
107 		GCLineWidth         , CARD16                ,
108 		GCLineStyle         , typeof(LineSolid)     ,
109 		GCCapStyle          , typeof(CapNotLast)    ,
110 		GCJoinStyle         , typeof(JoinMiter)     ,
111 		GCFillStyle         , typeof(FillSolid)     ,
112 		GCFillRule          , typeof(EvenOddRule)   ,
113 		GCTile              , Pixmap                ,
114 		GCStipple           , Pixmap                ,
115 		GCTileStipXOrigin   , INT16                 ,
116 		GCTileStipYOrigin   , INT16                 ,
117 		GCFont              , Font                  ,
118 		GCSubwindowMode     , typeof(ClipByChildren),
119 		GCGraphicsExposures , BOOL                  ,
120 		GCClipXOrigin       , INT16                 ,
121 		GCClipYOrigin       , INT16                 ,
122 		GCClipMask          , Pixmap                ,
123 		GCDashOffset        , CARD16                ,
124 		GCDashList          , CARD8                 ,
125 		GCArcMode           , typeof(ArcChord)      ,
126 	);
127 }
128 
129 /// `xReq` equivalent for requests with no arguments.
130 extern(C) struct xEmptyReq
131 {
132     CARD8 reqType; /// As in `xReq`.
133     CARD8 pad;     /// ditto
134     CARD16 length; /// ditto
135 }
136 
137 /// Base class for definitions shared by the core X11 protocol and
138 /// extensions.
139 private class X11SubProtocol
140 {
141 	struct RequestSpec(args...)
142 	if (args.length == 3)
143 	{
144 		enum reqType = args[0];
145 		enum reqName = __traits(identifier, args[0]);
146 		alias encoder = args[1];
147 		alias decoder = args[2];
148 		static assert(is(decoder == void) || !is(ReturnType!decoder == void));
149 		static if (is(decoder == void))
150 			alias ReturnType = void;
151 		else
152 			alias ReturnType = .ReturnType!decoder;
153 	}
154 
155 	struct EventSpec(args...)
156 	if (args.length == 2)
157 	{
158 		enum name = __traits(identifier, args[0]);
159 		enum type = args[0];
160 		alias decoder = args[1];
161 		alias ReturnType = .ReturnType!decoder;
162 	}
163 
164 	/// Instantiates to a function which accepts arguments and
165 	/// puts them into a struct, according to its fields.
166 	static template simpleEncoder(Req, string[] ignoreFields = [])
167 	{
168 		template isPertinentFieldIdx(size_t index)
169 		{
170 			enum name = __traits(identifier, Req.tupleof[index]);
171 			enum bool isPertinentFieldIdx =
172 				name != "reqType" &&
173 				name != "length" &&
174 				(name.length < 3 || name[0..min($, 3)] != "pad") &&
175 				!ignoreFields.contains(name);
176 		}
177 		alias FieldIdxType(size_t index) = typeof(Req.tupleof[index]);
178 		enum pertinentFieldIndices = Filter!(isPertinentFieldIdx, RangeTuple!(Req.tupleof.length));
179 
180 		Data simpleEncoder(
181 			staticMap!(FieldIdxType, pertinentFieldIndices) args,
182 		) {
183 			Req req;
184 
185 			foreach (i; RangeTuple!(args.length))
186 			{
187 				enum structIndex = pertinentFieldIndices[i];
188 				req.tupleof[structIndex] = args[i];
189 			}
190 
191 			return Data((&req)[0 .. 1]);
192 		}
193 	}
194 
195 	template simpleDecoder(Res)
196 	{
197 		template isPertinentFieldIdx(size_t index)
198 		{
199 			enum name = __traits(identifier, Res.tupleof[index]);
200 			enum bool isPertinentFieldIdx =
201 				name != "type" &&
202 				name != "sequenceNumber" &&
203 				name != "length" &&
204 				(name.length < 3 || name[0..min($, 3)] != "pad");
205 		}
206 		static struct DecodedResult
207 		{
208 			mixin((){
209 				import ae.utils.text.ascii : toDec;
210 
211 				string code;
212 				foreach (i; RangeTuple!(Res.tupleof.length))
213 					static if (isPertinentFieldIdx!i)
214 						code ~= `typeof(Res.tupleof)[` ~ toDec(i) ~ `] ` ~ __traits(identifier, Res.tupleof[i]) ~ ";";
215 				return code;
216 			}());
217 		}
218 		alias FieldIdxType(size_t index) = typeof(Res.tupleof[index]);
219 		enum pertinentFieldIndices = Filter!(isPertinentFieldIdx, RangeTuple!(Res.tupleof.length));
220 
221 		DecodedResult simpleDecoder(
222 			Data data,
223 		) {
224 			enforce(Res.sizeof < sz_xGenericReply || data.length == Res.sizeof,
225 				"Unexpected reply size");
226 			auto res = cast(Res*)data.contents.ptr;
227 
228 			DecodedResult result;
229 			foreach (i; RangeTuple!(pertinentFieldIndices.length))
230 			{
231 				result.tupleof[i] = res.tupleof[pertinentFieldIndices[i]];
232 				debug(X11) stderr.writeln("[X11] << ", __traits(identifier, result.tupleof[i]), ": ", result.tupleof[i]);
233 			}
234 
235 			return result;
236 		}
237 	}
238 
239 	static Data pad4(Data packet)
240 	{
241 		packet.length = (packet.length + 3) / 4 * 4;
242 		return packet;
243 	}
244 
245 	/// Generates code based on `RequestSpecs` and `EventSpecs`.
246 	/// Mix this into your extension definition to generate callable
247 	/// methods and event handlers.
248 	/// This mixin is also used to generate the core protocol glue code.
249 	mixin template ProtocolGlue()
250 	{
251 		import std.traits : Parameters, ReturnType;
252 		import std.exception : enforce;
253 		import ae.sys.data : Data;
254 		debug(X11) import std.stdio : stderr;
255 
256 		/// To avoid repetition, methods for sending packets and handlers for events are generated.
257 		/// This will generate senders such as sendCreateWindow,
258 		/// and event handlers such as handleExpose.
259 
260 		enum generatedCode = (){
261 			import ae.utils.text.ascii : toDec;
262 
263 			string code;
264 
265 			foreach (i, Spec; RequestSpecs)
266 			{
267 				enum index = toDec(i);
268 				assert(Spec.reqName[0 .. 2] == "X_");
269 				enum haveReply = !is(Spec.decoder == void);
270 				code ~= "public ";
271 				if (haveReply)
272 					code ~= "Promise!(ReturnType!(RequestSpecs[" ~ index ~ "].decoder))";
273 				else
274 					code ~= "void";
275 				code ~= " send" ~ Spec.reqName[2 .. $] ~ "(Parameters!(RequestSpecs[" ~ index ~ "].encoder) params) { ";
276 				debug(X11) code ~= " struct Request { typeof(params) p; } stderr.writeln(`[X11] > " ~ Spec.reqName ~ ": `, Request(params));";
277 				if (haveReply)
278 					code ~= `auto p = new typeof(return);`;
279 				code ~= "sendRequest(RequestSpecs[" ~ index ~ "].reqType, RequestSpecs[" ~ index ~ "].encoder(params), ";
280 				if (haveReply)
281 					debug (X11)
282 						code ~= "(Data data) { auto decoded = RequestSpecs[" ~ index ~ "].decoder(data); struct DecodedReply { typeof(decoded) d; } stderr.writeln(`[X11] < " ~ Spec.reqName ~ ": `, DecodedReply(decoded)); p.fulfill(decoded); }";
283 					else
284 						code ~= "(Data data) { p.fulfill(RequestSpecs[" ~ index ~ "].decoder(data)); }";
285 				else
286 					code ~= "null";
287 				code ~= ");";
288 				if (haveReply)
289 					code ~= "return p;";
290 				code ~=" }\n";
291 			}
292 
293 			foreach (i, Spec; EventSpecs)
294 			{
295 				enum index = toDec(i);
296 				code ~= "public void delegate(ReturnType!(EventSpecs[" ~ index ~ "].decoder)) handle" ~ Spec.name ~ ";\n";
297 				code ~= "private void _handle" ~ Spec.name ~ "(Data packet) {\n";
298 				code ~= "  enforce(handle" ~ Spec.name ~ ", `No event handler for event: " ~ Spec.name ~ "`);\n";
299 				debug (X11)
300 					code ~= "  auto decoded = EventSpecs[" ~ index ~ "].decoder(packet); struct DecodedEvent { typeof(decoded) d; } stderr.writeln(`[X11] < " ~ Spec.name ~ ": `, DecodedEvent(decoded)); return handle" ~ Spec.name ~ "(decoded);";
301 				else
302 					code ~= "  return handle" ~ Spec.name ~ "(EventSpecs[" ~ index ~ "].decoder(packet));\n";
303 				code ~= "}\n";
304 			}
305 
306 			code ~= "final private void registerEvents() {\n";
307 			foreach (i, Spec; EventSpecs)
308 				code ~= "  client.eventHandlers[firstEvent + " ~ toDec(Spec.type) ~ "] = &_handle" ~ Spec.name ~ ";\n";
309 			code ~= "}";
310 
311 			return code;
312 		}();
313 		// pragma(msg, generatedCode);
314 		mixin(generatedCode);
315 
316 		/// Helper version of `simpleEncoder` which skips the extension sub-opcode field
317 		// Note: this should be in the `static if` block below, but that causes a recursive template instantiation
318 		alias simpleExtEncoder(Req) = simpleEncoder!(Req, [reqTypeFieldName]);
319 
320 		// Extension helpers
321 		static if (is(typeof(this) : X11Extension))
322 		{
323 			import std.traits : ReturnType;
324 			import deimos.X11.Xproto : xQueryExtensionReply;
325 
326 			/// Forwarding constructor
327 			this(X11Client client, ReturnType!(simpleDecoder!xQueryExtensionReply) result)
328 			{
329 				super(client, result);
330 				registerEvents();
331 			}
332 
333 			/// Plumbing from encoder to `X11Client.sendRequest`
334 			private void sendRequest(BYTE reqType, Data requestData, void delegate(Data) handler)
335 			{
336 				assert(requestData.length >= BaseReq.sizeof);
337 				assert(requestData.length % 4 == 0);
338 				auto pReq = cast(BaseReq*)requestData.contents.ptr;
339 				mixin(`pReq.` ~ reqTypeFieldName ~ ` = reqType;`);
340 
341 				return client.sendRequest(majorOpcode, requestData, handler);
342 			}
343 		}
344 
345 		/// Returns the `RequestSpec` for the request with the indicated opcode.
346 		public template RequestSpecOf(CARD8 reqType)
347 		{
348 			static foreach (Spec; RequestSpecs)
349 				static if (Spec.reqType == reqType)
350 					alias RequestSpecOf = Spec;
351 		}
352 
353 		/// Returns the `EventSpec` for the event with the indicated opcode.
354 		public template EventSpecOf(CARD8 type)
355 		{
356 			static foreach (Spec; EventSpecs)
357 				static if (Spec.type == type)
358 					alias EventSpecOf = Spec;
359 		}
360 	}
361 }
362 
363 /// Implements the X11 protocol as a client.
364 /// Allows connecting to a local or remote X11 server.
365 ///
366 /// Note: Because of heavy use of code generation,
367 /// this class's API may be a little opaque.
368 /// You may instead find the example in demo/x11/demo.d
369 /// more illuminating.
370 final class X11Client : X11SubProtocol
371 {
372 	/// Connect to the default X server
373 	/// (according to `$DISPLAY`).
374 	this()
375 	{
376 		this(environment["DISPLAY"]);
377 	}
378 
379 	/// Connect to the server described by the specified display
380 	/// string.
381 	this(string display)
382 	{
383 		this(parseDisplayString(display));
384 		configureAuthentication(display);
385 	}
386 
387 	/// Parse a display string into connectable address specs.
388 	static AddressInfo[] parseDisplayString(string display)
389 	{
390 		auto hostParts = display.findSplit(":");
391 		enforce(hostParts, "Invalid display string: " ~ display);
392 		enforce(!hostParts[2].startsWith(":"), "DECnet is unsupported");
393 
394 		enforce(hostParts[2].length, "No display number"); // Not to be confused with the screen number
395 		auto displayNumber = hostParts[2].findSplit(".")[0];
396 
397 		string hostname = hostParts[0];
398 		AddressInfo[] result;
399 
400 		version (Posix) // Try UNIX sockets first
401 		if (!hostname.length)
402 			foreach (useAbstract; [true, false]) // Try abstract UNIX sockets first
403 			{
404 				version (linux) {} else continue;
405 				auto path = (useAbstract ? "\0" : "") ~ "/tmp/.X11-unix/X" ~ displayNumber;
406 				auto addr = new UnixAddress(path);
407 				result ~= AddressInfo(AddressFamily.UNIX, SocketType.STREAM, cast(ProtocolType)0, addr, path);
408 			}
409 
410 		if (!hostname.length)
411 			hostname = "localhost";
412 
413 		result ~= getAddressInfo(hostname, (X_TCP_PORT + displayNumber.to!ushort).to!string);
414 		return result;
415 	}
416 
417 	/// Connect to the given address specs.
418 	this(AddressInfo[] ai)
419 	{
420 		replyHandlers.length = 0x1_0000; // Allocate dynamically, to avoid bloating this.init
421 		registerEvents();
422 
423 		conn = new SocketConnection;
424 		conn.handleConnect = &onConnect;
425 		conn.handleReadData = &onReadData;
426 		conn.connect(ai);
427 	}
428 
429 	SocketConnection conn; /// Underlying connection.
430 
431 	void delegate() handleConnect; /// Handler for when a connection is successfully established.
432 	@property void handleDisconnect(void delegate(string, DisconnectType) dg) { conn.handleDisconnect = dg; } /// Setter for a disconnect handler.
433 
434 	void delegate(scope ref const xError error) handleError; /// Error handler
435 
436 	void delegate(Data event) handleGenericEvent; /// GenericEvent handler
437 
438 	/// Authentication information used during connection.
439 	string authorizationProtocolName;
440 	ubyte[] authorizationProtocolData; /// ditto
441 
442 	private import std.stdio : File;
443 
444 	/// Automatically attempt to configure authentication by reading ~/.Xauthority.
445 	void configureAuthentication(string display)
446 	{
447 		import std.path : expandTilde, buildPath;
448 		import std.socket : Socket;
449 		import ae.sys.paths : getHomeDir;
450 
451 		auto hostParts = display.findSplit(":");
452 		auto host = hostParts[0];
453 		if (!host.length)
454 			return;
455 		if (host == "localhost")
456 			host = Socket.hostName;
457 
458 		auto number = hostParts[2];
459 		number = number.findSplit(".")[0];
460 
461 		foreach (ref record; XAuthorityReader(File(getHomeDir.buildPath(".Xauthority"), "rb")))
462 			if (record.address == host && record.number == number)
463 			{
464 				authorizationProtocolName = record.name;
465 				authorizationProtocolData = record.data;
466 				return;
467 			}
468 	}
469 
470 	/// Xauthority parsing.
471 	struct AuthRecord
472 	{
473 		ushort family;
474 		string address;
475 		string number;
476 		string name;
477 		ubyte[] data;
478 	}
479 
480 	struct XAuthorityReader
481 	{
482 		File f;
483 
484 		this(File f) { this.f = f; popFront(); }
485 
486 		AuthRecord front;
487 		bool empty;
488 
489 		class EndOfFile : Throwable { this() { super(null); } }
490 
491 		void popFront()
492 		{
493 			bool atStart = true;
494 
495 			ushort readShort()
496 			{
497 				import ae.utils.bitmanip : BigEndian;
498 				import ae.sys.file : readExactly;
499 
500 				BigEndian!ushort[1] result;
501 				if (!f.readExactly(result.bytes[]))
502 					throw atStart ? new EndOfFile : new Exception("Unexpected end of file");
503 				atStart = false;
504 				return result[0];
505 			}
506 
507 			ubyte[] readBytes()
508 			{
509 				auto length = readShort();
510 				auto result = new ubyte[length];
511 				auto bytesRead = f.rawRead(result);
512 				enforce(bytesRead.length == length, "Unexpected end of file");
513 				return result;
514 			}
515 
516 			try
517 			{
518 				front.family = readShort();
519 				front.address = readBytes().fromBytes!string;
520 				front.number = readBytes().fromBytes!string;
521 				front.name = readBytes().fromBytes!string;
522 				front.data = readBytes();
523 			}
524 			catch (EndOfFile)
525 				empty = true;
526 		}
527 	} // ditto
528 
529 	/// Connection information received from the server.
530 	xConnSetupPrefix connSetupPrefix;
531 	xConnSetup connSetup; /// ditto
532 	string vendor; /// ditto
533 	immutable(xPixmapFormat)[] pixmapFormats; /// ditto
534 	struct Root
535 	{
536 		xWindowRoot root; /// Root window information.
537 		struct Depth
538 		{
539 			xDepth depth; /// Color depth information.
540 			immutable(xVisualType)[] visualTypes; /// ditto
541 		} /// Supported depths.
542 		Depth[] depths; /// ditto
543 	} /// ditto
544 	Root[] roots; /// ditto
545 
546 	/// Generate a new resource ID, which can be used
547 	/// to identify resources created by this connection.
548 	CARD32 newRID()
549 	{
550 		auto counter = ridCounter++;
551 		CARD32 rid = connSetup.ridBase;
552 		foreach (ridBit; 0 .. typeof(rid).sizeof * 8)
553 		{
554 			auto ridMask = CARD32(1) << ridBit;
555 			if (connSetup.ridMask & ridMask) // May we use this bit?
556 			{
557 				auto bit = counter & 1;
558 				counter >>= 1;
559 				rid |= bit << ridBit;
560 			}
561 		}
562 		enforce(counter == 0, "RID counter overflow - too many RIDs");
563 		return rid;
564 	}
565 
566 	// For internal use. Used by extension implementations to register
567 	// low level event handlers, via the `ProtocolGlue` mixin.
568 	// Clients should use the handleXXX functions.
569 	/*private*/ void delegate(Data packet)[0x80] eventHandlers;
570 
571 	/// Request an extension.
572 	/// The promise is fulfilled with an instance of the extension
573 	/// bound to the current connection, or null if the extension is
574 	/// not available.
575 	Promise!Ext requestExtension(Ext : X11Extension)()
576 	{
577 		return sendQueryExtension(Ext.name)
578 			.dmd21804workaround
579 			.then((result) {
580 				if (result.present)
581 					return new Ext(this, result);
582 				else
583 					return null;
584 			});
585 	}
586 
587 private:
588 	alias RequestSpecs = AliasSeq!(
589 		RequestSpec!(
590 			X_CreateWindow,
591 			function Data (
592 				// Request struct members
593 				CARD8 depth, 
594 				Window wid,
595 				Window parent,
596 				INT16 x,
597 				INT16 y,
598 				CARD16 width,
599 				CARD16 height,
600 				CARD16 borderWidth,
601 				CARD16 c_class,
602 				VisualID visual,
603 
604 				// Optional parameters whose presence
605 				// is indicated by a bit mask
606 				in ref WindowAttributes windowAttributes,
607 			) {
608 				CARD32 mask;
609 				auto values = windowAttributes._serialize(mask);
610 				mixin(populateRequestFromLocals!xCreateWindowReq);
611 				return Data(req.bytes) ~ Data(values.bytes);
612 			},
613 			void,
614 		),
615 
616 		RequestSpec!(
617 			X_ChangeWindowAttributes,
618 			function Data (
619 				// Request struct members
620 				Window window,
621 
622 				// Optional parameters whose presence
623 				// is indicated by a bit mask
624 				in ref WindowAttributes windowAttributes,
625 			) {
626 				CARD32 valueMask;
627 				auto values = windowAttributes._serialize(valueMask);
628 				mixin(populateRequestFromLocals!xChangeWindowAttributesReq);
629 				return Data(req.bytes) ~ Data(values.bytes);
630 			},
631 			void,
632 		),
633 
634 		RequestSpec!(
635 			X_GetWindowAttributes,
636 			simpleEncoder!xResourceReq,
637 			simpleDecoder!xGetWindowAttributesReply,
638 		),
639 
640 		RequestSpec!(
641 			X_DestroyWindow,
642 			simpleEncoder!xResourceReq,
643 			void,
644 		),
645 
646 		RequestSpec!(
647 			X_DestroySubwindows,
648 			simpleEncoder!xResourceReq,
649 			void,
650 		),
651 
652 		RequestSpec!(
653 			X_ChangeSaveSet,
654 			simpleEncoder!xChangeSaveSetReq,
655 			void,
656 		),
657 
658 		RequestSpec!(
659 			X_ReparentWindow,
660 			simpleEncoder!xReparentWindowReq,
661 			void,
662 		),
663 
664 		RequestSpec!(
665 			X_MapWindow,
666 			simpleEncoder!xResourceReq,
667 			void,
668 		),
669 
670 		RequestSpec!(
671 			X_MapSubwindows,
672 			simpleEncoder!xResourceReq,
673 			void,
674 		),
675 
676 		RequestSpec!(
677 			X_UnmapWindow,
678 			simpleEncoder!xResourceReq,
679 			void,
680 		),
681 
682 		RequestSpec!(
683 			X_UnmapSubwindows,
684 			simpleEncoder!xResourceReq,
685 			void,
686 		),
687 
688 		RequestSpec!(
689 			X_ConfigureWindow,
690 			function Data (
691 				// Request struct members
692 				Window window,
693 
694 				// Optional parameters whose presence
695 				// is indicated by a bit mask
696 				in ref WindowConfiguration windowConfiguration,
697 			) {
698 				CARD16 mask;
699 				auto values = windowConfiguration._serialize(mask);
700 				mixin(populateRequestFromLocals!xConfigureWindowReq);
701 				return Data(req.bytes) ~ Data(values.bytes);
702 			},
703 			void,
704 		),
705 
706 		RequestSpec!(
707 			X_CirculateWindow,
708 			simpleEncoder!xCirculateWindowReq,
709 			void,
710 		),
711 
712 		RequestSpec!(
713 			X_GetGeometry,
714 			simpleEncoder!xResourceReq,
715 			simpleDecoder!xGetGeometryReply,
716 		),
717 
718 		RequestSpec!(
719 			X_QueryTree,
720 			simpleEncoder!xResourceReq,
721 			(Data data)
722 			{
723 				auto reader = DataReader(data);
724 				auto header = *reader.read!xQueryTreeReply().enforce("Unexpected reply size");
725 				auto children = reader.read!Window(header.nChildren).enforce("Unexpected reply size");
726 				enforce(reader.data.length == 0, "Unexpected reply size");
727 				struct Result
728 				{
729 					Window root;
730 					Window parent;
731 					Window[] children;
732 				}
733 				return Result(
734 					header.root,
735 					header.parent,
736 					children.toHeap(),
737 				);
738 			}
739 		),
740 
741 		RequestSpec!(
742 			X_InternAtom,
743 			function Data (
744 				// Request struct members
745 				bool onlyIfExists,
746 
747 				// Extra data
748 				const(char)[] name,
749 
750 			) {
751 				auto nbytes = name.length.to!CARD16;
752 				mixin(populateRequestFromLocals!xInternAtomReq);
753 				return pad4(Data(req.bytes) ~ Data(name.bytes));
754 			},
755 			simpleDecoder!xInternAtomReply,
756 		),
757 
758 		RequestSpec!(
759 			X_GetAtomName,
760 			simpleEncoder!xResourceReq,
761 			(Data data)
762 			{
763 				auto reader = DataReader(data);
764 				auto header = *reader.read!xGetAtomNameReply().enforce("Unexpected reply size");
765 				auto name = reader.read!char(header.nameLength).enforce("Unexpected reply size");
766 				enforce(reader.data.length < 4, "Unexpected reply size");
767 
768 				return name.toHeap();
769 			}
770 		),
771 
772 		RequestSpec!(
773 			X_ChangeProperty,
774 			function Data (
775 				// Request struct members
776 				CARD8 mode,
777 				Window window,
778 				Atom property,
779 				Atom type,
780 				CARD8 format,
781 
782 				// Extra data
783 				const(ubyte)[] data,
784 
785 			) {
786 				auto nUnits = (data.length * 8 / format).to!CARD32;
787 				mixin(populateRequestFromLocals!xChangePropertyReq);
788 				return pad4(Data(req.bytes) ~ Data(data.bytes));
789 			},
790 			void,
791 		),
792 
793 		RequestSpec!(
794 			X_DeleteProperty,
795 			simpleEncoder!xDeletePropertyReq,
796 			void,
797 		),
798 
799 		RequestSpec!(
800 			X_GetProperty,
801 			simpleEncoder!xGetPropertyReq,
802 			(Data data)
803 			{
804 				auto reader = DataReader(data);
805 				auto header = *reader.read!xGetPropertyReply().enforce("Unexpected reply size");
806 				auto dataLength = header.nItems * header.format / 8;
807 				auto value = reader.read!ubyte(dataLength).enforce("Unexpected reply size");
808 				enforce(reader.data.length < 4, "Unexpected reply size");
809 
810 				struct Result
811 				{
812 					CARD8 format;
813 					Atom propertyType;
814 					CARD32 bytesAfter;
815 					const(ubyte)[] value;
816 				}
817 				return Result(
818 					header.format,
819 					header.propertyType,
820 					header.bytesAfter,
821 					value.toHeap(),
822 				);
823 			}
824 		),
825 
826 		RequestSpec!(
827 			X_ListProperties,
828 			simpleEncoder!xResourceReq,
829 			(Data data)
830 			{
831 				auto reader = DataReader(data);
832 				auto header = *reader.read!xListPropertiesReply().enforce("Unexpected reply size");
833 				auto atoms = reader.read!Atom(header.nProperties).enforce("Unexpected reply size");
834 				enforce(reader.data.length < 4, "Unexpected reply size");
835 
836 				return atoms.toHeap();
837 			}
838 		),
839 
840 		RequestSpec!(
841 			X_SetSelectionOwner,
842 			simpleEncoder!xSetSelectionOwnerReq,
843 			void,
844 		),
845 
846 		RequestSpec!(
847 			X_GetSelectionOwner,
848 			simpleEncoder!xResourceReq,
849 			simpleDecoder!xGetSelectionOwnerReply,
850 		),
851 
852 		RequestSpec!(
853 			X_ConvertSelection,
854 			simpleEncoder!xConvertSelectionReq,
855 			void,
856 		),
857 
858 		RequestSpec!(
859 			X_SendEvent,
860 			function Data (
861 				bool propagate,
862 				Window destination,
863 				CARD32 eventMask,
864 				xEvent event,
865 			) {
866 				auto eventdata = cast(byte[event.sizeof])event.bytes[0 .. event.sizeof];
867 				mixin(populateRequestFromLocals!xSendEventReq);
868 				return Data(req.bytes);
869 			},
870 			void,
871 		),
872 
873 		RequestSpec!(
874 			X_GrabPointer,
875 			simpleEncoder!xGrabPointerReq,
876 			simpleDecoder!xGrabPointerReply,
877 		),
878 
879 		RequestSpec!(
880 			X_UngrabPointer,
881 			simpleEncoder!xResourceReq,
882 			void,
883 		),
884 
885 		RequestSpec!(
886 			X_GrabButton,
887 			simpleEncoder!xGrabButtonReq,
888 			void,
889 		),
890 
891 		RequestSpec!(
892 			X_UngrabButton,
893 			simpleEncoder!xUngrabButtonReq,
894 			void,
895 		),
896 
897 		RequestSpec!(
898 			X_ChangeActivePointerGrab,
899 			simpleEncoder!xChangeActivePointerGrabReq,
900 			void,
901 		),
902 
903 		RequestSpec!(
904 			X_GrabKeyboard,
905 			simpleEncoder!xGrabKeyboardReq,
906 			simpleDecoder!xGrabKeyboardReply,
907 		),
908 
909 		RequestSpec!(
910 			X_UngrabKeyboard,
911 			simpleEncoder!xResourceReq,
912 			void,
913 		),
914 
915 		RequestSpec!(
916 			X_GrabKey,
917 			simpleEncoder!xGrabKeyReq,
918 			void,
919 		),
920 
921 		RequestSpec!(
922 			X_UngrabKey,
923 			simpleEncoder!xUngrabKeyReq,
924 			void,
925 		),
926 
927 		RequestSpec!(
928 			X_AllowEvents,
929 			simpleEncoder!xAllowEventsReq,
930 			void,
931 		),
932 
933 		RequestSpec!(
934 			X_GrabServer,
935 			simpleEncoder!xEmptyReq,
936 			void,
937 		),
938 
939 		RequestSpec!(
940 			X_UngrabServer,
941 			simpleEncoder!xEmptyReq,
942 			void,
943 		),
944 
945 		RequestSpec!(
946 			X_QueryPointer,
947 			simpleEncoder!xResourceReq,
948 			simpleDecoder!xQueryPointerReply,
949 		),
950 
951 		RequestSpec!(
952 			X_GetMotionEvents,
953 			simpleEncoder!xGetMotionEventsReq,
954 			(Data data)
955 			{
956 				auto reader = DataReader(data);
957 				auto header = *reader.read!xGetMotionEventsReply().enforce("Unexpected reply size");
958 				auto events = reader.read!xTimecoord(header.nEvents).enforce("Unexpected reply size");
959 				enforce(reader.data.length == 0, "Unexpected reply size");
960 
961 				return events.arr.idup;
962 			}
963 		),
964 
965 		RequestSpec!(
966 			X_TranslateCoords,
967 			simpleEncoder!xTranslateCoordsReq,
968 			simpleDecoder!xTranslateCoordsReply,
969 		),
970 
971 		RequestSpec!(
972 			X_WarpPointer,
973 			simpleEncoder!xWarpPointerReq,
974 			void,
975 		),
976 
977 		RequestSpec!(
978 			X_SetInputFocus,
979 			simpleEncoder!xSetInputFocusReq,
980 			void,
981 		),
982 
983 		RequestSpec!(
984 			X_GetInputFocus,
985 			simpleEncoder!xEmptyReq,
986 			simpleDecoder!xGetInputFocusReply,
987 		),
988 
989 		RequestSpec!(
990 			X_QueryKeymap,
991 			simpleEncoder!xEmptyReq,
992 			simpleDecoder!xQueryKeymapReply,
993 		),
994 
995 		RequestSpec!(
996 			X_OpenFont,
997 			simpleEncoder!xOpenFontReq,
998 			void,
999 		),
1000 
1001 		RequestSpec!(
1002 			X_CloseFont,
1003 			simpleEncoder!xResourceReq,
1004 			void,
1005 		),
1006 
1007 		// RequestSpec!(
1008 		// 	X_QueryFont,
1009 		// 	simpleEncoder!xResourceReq,
1010 		// 	TODO
1011 		// ),
1012 
1013 		// ...
1014 
1015 		RequestSpec!(
1016 			X_CreateGC,
1017 			function Data (
1018 				// Request struct members
1019 				GContext gc,
1020 				Drawable drawable,
1021 
1022 				// Optional parameters whose presence
1023 				// is indicated by a bit mask
1024 				in ref GCAttributes gcAttributes,
1025 			) {
1026 				CARD32 mask;
1027 				auto values = gcAttributes._serialize(mask);
1028 				mixin(populateRequestFromLocals!xCreateGCReq);
1029 				return Data(req.bytes) ~ Data(values.bytes);
1030 			},
1031 			void,
1032 		),
1033 
1034 		// ...
1035 
1036 		RequestSpec!(
1037 			X_ImageText8,
1038 			function Data (
1039 				// Request struct members
1040 				Drawable drawable,
1041 				GContext gc,
1042 				INT16 x,
1043 				INT16 y,
1044 
1045 				// Extra data
1046 				const(char)[] string,
1047 
1048 			) {
1049 				auto nChars = string.length.to!ubyte;
1050 				mixin(populateRequestFromLocals!xImageText8Req);
1051 				return pad4(Data(req.bytes) ~ Data(string.bytes));
1052 			},
1053 			void,
1054 		),
1055 
1056 		// ...
1057 
1058 		RequestSpec!(
1059 			X_PolyFillRectangle,
1060 			function Data (
1061 				// Request struct members
1062 				Drawable drawable,
1063 				GContext gc,
1064 
1065 				// Extra data
1066 				const(xRectangle)[] rectangles,
1067 
1068 			) {
1069 				mixin(populateRequestFromLocals!xPolyFillRectangleReq);
1070 				return Data(req.bytes) ~ Data(rectangles.bytes);
1071 			},
1072 			void,
1073 		),
1074 
1075 		// ...
1076 
1077 		RequestSpec!(
1078 			X_QueryExtension,
1079 			function Data (
1080 				// Extra data
1081 				const(char)[] name,
1082 
1083 			) {
1084 				auto nbytes = name.length.to!ubyte;
1085 				mixin(populateRequestFromLocals!xQueryExtensionReq);
1086 				return pad4(Data(req.bytes) ~ Data(name.bytes));
1087 			},
1088 			simpleDecoder!xQueryExtensionReply,
1089 		),
1090 
1091 		// ...
1092 	);
1093 
1094 	alias EventSpecs = AliasSeq!(
1095 		EventSpec!(KeyPress        , simpleDecoder!(xEvent.KeyButtonPointer)),
1096 		EventSpec!(KeyRelease      , simpleDecoder!(xEvent.KeyButtonPointer)),
1097 		EventSpec!(ButtonPress     , simpleDecoder!(xEvent.KeyButtonPointer)),
1098 		EventSpec!(ButtonRelease   , simpleDecoder!(xEvent.KeyButtonPointer)),
1099 		EventSpec!(MotionNotify    , simpleDecoder!(xEvent.KeyButtonPointer)),
1100 		EventSpec!(EnterNotify     , simpleDecoder!(xEvent.EnterLeave      )),
1101 		EventSpec!(LeaveNotify     , simpleDecoder!(xEvent.EnterLeave      )),
1102 		EventSpec!(FocusIn         , simpleDecoder!(xEvent.Focus           )),
1103 		EventSpec!(FocusOut        , simpleDecoder!(xEvent.Focus           )),
1104 		EventSpec!(KeymapNotify    , simpleDecoder!(xKeymapEvent           )),
1105 		EventSpec!(Expose          , simpleDecoder!(xEvent.Expose          )),
1106 		EventSpec!(GraphicsExpose  , simpleDecoder!(xEvent.GraphicsExposure)),
1107 		EventSpec!(NoExpose        , simpleDecoder!(xEvent.NoExposure      )),
1108 		EventSpec!(VisibilityNotify, simpleDecoder!(xEvent.Visibility      )),
1109 		EventSpec!(CreateNotify    , simpleDecoder!(xEvent.CreateNotify    )),
1110 		EventSpec!(DestroyNotify   , simpleDecoder!(xEvent.DestroyNotify   )),
1111 		EventSpec!(UnmapNotify     , simpleDecoder!(xEvent.UnmapNotify     )),
1112 		EventSpec!(MapNotify       , simpleDecoder!(xEvent.MapNotify       )),
1113 		EventSpec!(MapRequest      , simpleDecoder!(xEvent.MapRequest      )),
1114 		EventSpec!(ReparentNotify  , simpleDecoder!(xEvent.Reparent        )),
1115 		EventSpec!(ConfigureNotify , simpleDecoder!(xEvent.ConfigureNotify )),
1116 		EventSpec!(ConfigureRequest, simpleDecoder!(xEvent.ConfigureRequest)),
1117 		EventSpec!(GravityNotify   , simpleDecoder!(xEvent.Gravity         )),
1118 		EventSpec!(ResizeRequest   , simpleDecoder!(xEvent.ResizeRequest   )),
1119 		EventSpec!(CirculateNotify , simpleDecoder!(xEvent.Circulate       )),
1120 		EventSpec!(CirculateRequest, simpleDecoder!(xEvent.Circulate       )),
1121 		EventSpec!(PropertyNotify  , simpleDecoder!(xEvent.Property        )),
1122 		EventSpec!(SelectionClear  , simpleDecoder!(xEvent.SelectionClear  )),
1123 		EventSpec!(SelectionRequest, simpleDecoder!(xEvent.SelectionRequest)),
1124 		EventSpec!(SelectionNotify , simpleDecoder!(xEvent.SelectionNotify )),
1125 		EventSpec!(ColormapNotify  , simpleDecoder!(xEvent.Colormap        )),
1126 		EventSpec!(ClientMessage   ,
1127 			function(
1128 				Data data,
1129 			) {
1130 				auto reader = DataReader(data);
1131 				auto packet = *reader.read!(xEvent.ClientMessage)().enforce("Unexpected reply size");
1132 				struct Result
1133 				{
1134 					Atom type;
1135 					ubyte[20] bytes;
1136 				}
1137 				return Result(
1138 					packet.b.type,
1139 					cast(ubyte[20])packet.b.bytes,
1140 				);
1141 			}
1142 		),
1143 		EventSpec!(MappingNotify   , simpleDecoder!(xEvent.MappingNotify   )),
1144 	//	EventSpec!(GenericEvent    , simpleDecoder!(xGenericEvent          )),
1145 	);
1146 
1147 	@property X11Client client() { return this; }
1148 	enum firstEvent = 0;
1149 
1150 	mixin ProtocolGlue;
1151 
1152 	void onConnect()
1153 	{
1154 		xConnClientPrefix prefix;
1155 		version (BigEndian)
1156 			prefix.byteOrder = 'B';
1157 		version (LittleEndian)
1158 			prefix.byteOrder = 'l';
1159 		prefix.majorVersion = X_PROTOCOL;
1160 		prefix.minorVersion = X_PROTOCOL_REVISION;
1161 		prefix.nbytesAuthProto6 = authorizationProtocolName.length.to!CARD16;
1162 		prefix.nbytesAuthString = authorizationProtocolData.length.to!CARD16;
1163 
1164 		conn.send(Data(prefix.bytes));
1165 		conn.send(pad4(Data(authorizationProtocolName)));
1166 		conn.send(pad4(Data(authorizationProtocolData)));
1167 	}
1168 
1169 	Data buffer;
1170 
1171 	bool connected;
1172 
1173 	ushort sequenceNumber = 1;
1174 	void delegate(Data)[] replyHandlers;
1175 
1176 	uint ridCounter; // for newRID
1177 
1178 	void onReadData(Data data)
1179 	{
1180 		buffer ~= data;
1181 
1182 		try
1183 			while (true)
1184 			{
1185 				if (!connected)
1186 				{
1187 					auto reader = DataReader(buffer);
1188 
1189 					auto pConnSetupPrefix = reader.read!xConnSetupPrefix();
1190 					if (!pConnSetupPrefix)
1191 						return;
1192 
1193 					auto additionalBytes = reader.read!uint((*pConnSetupPrefix).length);
1194 					if (!additionalBytes)
1195 						return;
1196 					auto additionalReader = DataReader(additionalBytes.data);
1197 
1198 					connSetupPrefix = *pConnSetupPrefix;
1199 					switch ((*pConnSetupPrefix).success)
1200 					{
1201 						case 0: // Failed
1202 						{
1203 							auto reason = additionalReader.read!char((*pConnSetupPrefix).lengthReason)
1204 								.enforce("Insufficient bytes for reason");
1205 							conn.disconnect("X11 connection failed: " ~ cast(string)reason.arr, DisconnectType.error);
1206 							break;
1207 						}
1208 						case 1: // Success
1209 						{
1210 							auto pConnSetup = additionalReader.read!xConnSetup()
1211 								.enforce("Connection setup packet too short");
1212 							connSetup = *pConnSetup;
1213 
1214 							auto vendorBytes = additionalReader.read!uint(((*pConnSetup).nbytesVendor + 3) / 4)
1215 								.enforce("Connection setup packet too short");
1216 							this.vendor = DataReader(vendorBytes.data).read!char((*pConnSetup).nbytesVendor).arr.idup;
1217 
1218 							// scope(failure) { import std.stdio, ae.utils.json; writeln(connSetupPrefix.toPrettyJson); writeln(connSetup.toPrettyJson); writeln(pixmapFormats.toPrettyJson); writeln(roots.toPrettyJson); }
1219 
1220 							this.pixmapFormats =
1221 								additionalReader.read!xPixmapFormat((*pConnSetup).numFormats)
1222 								.enforce("Connection setup packet too short")
1223 								.arr.idup;
1224 							foreach (i; 0 .. (*pConnSetup).numRoots)
1225 							{
1226 								Root root;
1227 								// scope(failure) { import std.stdio, ae.utils.json; writeln(root.toPrettyJson); }
1228 								root.root = *additionalReader.read!xWindowRoot()
1229 									.enforce("Connection setup packet too short");
1230 								foreach (j; 0 .. root.root.nDepths)
1231 								{
1232 									Root.Depth depth;
1233 									depth.depth = *additionalReader.read!xDepth()
1234 										.enforce("Connection setup packet too short");
1235 									depth.visualTypes = additionalReader.read!xVisualType(depth.depth.nVisuals)
1236 										.enforce("Connection setup packet too short")
1237 										.arr.idup;
1238 									root.depths ~= depth;
1239 								}
1240 								this.roots ~= root;
1241 							}
1242 
1243 							enforce(!additionalReader.data.length,
1244 								"Left-over bytes in connection setup packet");
1245 
1246 							connected = true;
1247 							if (handleConnect)
1248 								handleConnect();
1249 
1250 							break;
1251 						}
1252 						case 2: // Authenticate
1253 						{
1254 							auto reason = additionalReader.read!char((*pConnSetupPrefix).lengthReason)
1255 								.enforce("Insufficient bytes for reason");
1256 							conn.disconnect("X11 authentication required: " ~ cast(string)reason.arr, DisconnectType.error);
1257 							break;
1258 						}
1259 						default:
1260 							throw new Exception("Unknown connection success code");
1261 					}
1262 
1263 					buffer = reader.data;
1264 				}
1265 
1266 				if (connected)
1267 				{
1268 					auto reader = DataReader(buffer);
1269 
1270 					auto pGenericReply = reader.peek!xGenericReply();
1271 					if (!pGenericReply)
1272 						return;
1273 
1274 					Data packet;
1275 
1276 					switch ((*pGenericReply).type)
1277 					{
1278 						case X_Error:
1279 						default:
1280 							packet = reader.read!xGenericReply().data;
1281 							assert(packet);
1282 							break;
1283 						case X_Reply:
1284 						case GenericEvent:
1285 							packet = reader.read!uint(8 + (*pGenericReply).length).data;
1286 							if (!packet)
1287 								return;
1288 							break;
1289 					}
1290 
1291 					switch ((*pGenericReply).type)
1292 					{
1293 						case X_Error:
1294 							if (handleError)
1295 								handleError(*DataReader(packet).read!xError);
1296 							else
1297 							{
1298 								debug (X11) stderr.writeln(*DataReader(packet).read!xError);
1299 								throw new Exception("Protocol error");
1300 							}
1301 							break;
1302 						case X_Reply:
1303 							onReply(packet);
1304 							break;
1305 						case GenericEvent:
1306 							if (handleGenericEvent)
1307 								handleGenericEvent(packet);
1308 							break;
1309 						default:
1310 							onEvent(packet);
1311 					}
1312 
1313 					buffer = reader.data;
1314 				}
1315 			}
1316 		catch (CaughtException e)
1317 			conn.disconnect(e.msg, DisconnectType.error);
1318 	}
1319 
1320 	void onReply(Data packet)
1321 	{
1322 		auto pHeader = DataReader(packet).peek!xGenericReply;
1323 		auto handler = replyHandlers[(*pHeader).sequenceNumber];
1324 		enforce(handler !is null,
1325 			"Unexpected packet");
1326 		replyHandlers[(*pHeader).sequenceNumber] = null;
1327 		handler(packet);
1328 	}
1329 
1330 	void onEvent(Data packet)
1331 	{
1332 		auto pEvent = DataReader(packet).peek!xEvent;
1333 		auto eventType = (*pEvent).u.type & 0x7F;
1334 		// bool artificial = !!((*pEvent).u.type >> 7);
1335 		auto handler = eventHandlers[eventType];
1336 		if (!handler)
1337 			throw new Exception("Unrecognized event: " ~ eventType.to!string);
1338 		return handler(packet);
1339 	}
1340 
1341 	/*private*/ public void sendRequest(BYTE reqType, Data requestData, void delegate(Data) handler)
1342 	{
1343 		assert(requestData.length >= sz_xReq);
1344 		assert(requestData.length % 4 == 0);
1345 		auto pReq = cast(xReq*)requestData.contents.ptr;
1346 		pReq.reqType = reqType;
1347 		pReq.length = (requestData.length / 4).to!ushort;
1348 
1349 		enforce(replyHandlers[sequenceNumber] is null,
1350 			"Sequence number overflow"); // We haven't yet received a reply from the previous cycle
1351 		replyHandlers[sequenceNumber] = handler;
1352 		conn.send(requestData);
1353 		sequenceNumber++;
1354 	}
1355 }
1356 
1357 // ************************************************************************
1358 
1359 /// Base class for X11 extensions.
1360 abstract class X11Extension : X11SubProtocol
1361 {
1362 	X11Client client;
1363 	CARD8 majorOpcode, firstEvent, firstError;
1364 
1365 	this(X11Client client, ReturnType!(simpleDecoder!xQueryExtensionReply) reply)
1366 	{
1367 		this.client = client;
1368 		this.majorOpcode = reply.major_opcode;
1369 		this.firstEvent = reply.first_event;
1370 		this.firstError = reply.first_error;
1371 	}
1372 }
1373 
1374 /// Example extension:
1375 version (none)
1376 class XEXAMPLE : X11Extension
1377 {
1378 	/// Mix this in to generate sendXXX and handleXXX declarations.
1379 	mixin ProtocolGlue;
1380 
1381 	/// The name by which to request the extension (as in X_QueryExtension).
1382 	enum name = XEXAMPLE_NAME;
1383 	/// The extension's base request type.
1384 	alias BaseReq = xXExampleReq;
1385 	/// The name of the field encoding the extension's opcode.
1386 	enum reqTypeFieldName = q{xexampleReqType};
1387 
1388 	/// Declare the extension's requests here.
1389 	alias RequestSpecs = AliasSeq!(
1390 		RequestSpec!(
1391 			X_XExampleRequest,
1392 			simpleExtEncoder!xXExampleRequestReq,
1393 			simpleDecoder!xXExampleRequestReply,
1394 		),
1395 	);
1396 
1397 	/// Declare the extension's events here.
1398 	alias EventSpecs = AliasSeq!(
1399 		EventSpec!(XExampleNotify, simpleDecoder!xXExampleNotifyEvent),
1400 	);
1401 }
1402 
1403 // ************************************************************************
1404 
1405 private:
1406 
1407 mixin template Optionals(args...)
1408 if (args.length % 2 == 0)
1409 {
1410 	alias _args = args; // DMD bug workaround
1411 
1412 	private mixin template Field(size_t i)
1413 	{
1414 		alias Type = args[i * 2 + 1];
1415 		enum maskName = __traits(identifier, args[i * 2]);
1416 		enum fieldName = toLower(maskName[2]) ~ maskName[3 .. $];
1417 		enum prefix = is(typeof(mixin("(){int " ~ fieldName ~ "; }()"))) ? "" : "c_";
1418 		mixin(`Nullable!Type ` ~ prefix ~ fieldName ~ ';');
1419 	}
1420 
1421 	static foreach (i; 0 .. args.length / 2)
1422 		mixin Field!i;
1423 
1424 	CARD32[] _serialize(Mask)(ref Mask mask) const
1425 	{
1426 		CARD32[] result;
1427 		static foreach (i; 0 .. _args.length / 2)
1428 			if (!this.tupleof[i].isNull)
1429 			{
1430 				enum fieldMask = _args[i * 2];
1431 				assert(mask < fieldMask);
1432 				result ~= this.tupleof[i].get();
1433 				mask |= fieldMask;
1434 			}
1435 		return result;
1436 	}
1437 }
1438 
1439 /// Generate code to populate all of a request struct's fields from arguments / locals.
1440 string populateRequestFromLocals(T)()
1441 {
1442 	string code = T.stringof ~ " req;\n";
1443 	foreach (i; RangeTuple!(T.tupleof.length))
1444 	{
1445 		enum name = __traits(identifier, T.tupleof[i]);
1446 		enum isPertinentField =
1447 			name != "reqType" &&
1448 			name != "length" &&
1449 			(name.length < 3 || name[0..min($, 3)] != "pad");
1450 		if (isPertinentField)
1451 		{
1452 			code ~= "req." ~ name ~ " = " ~ name ~ ";\n";
1453 			debug(X11) code ~= `stderr.writeln("[X11] >> ` ~ name ~ `: ", ` ~ name ~ `);`;
1454 		}
1455 	}
1456 	return code;
1457 }
1458 
1459 // ************************************************************************
1460 
1461 /// Typed wrapper for Data.
1462 /// Because Data is reference counted, this type allows encapsulating
1463 /// a safe but typed reference to a Data slice.
1464 struct DataObject(T)
1465 if (!hasIndirections!T)
1466 {
1467 	Data data;
1468 
1469 	T opCast(T : bool)() const
1470 	{
1471 		return !!data;
1472 	}
1473 
1474 	@property T* ptr()
1475 	{
1476 		assert(data && data.length == T.sizeof);
1477 		return cast(T*)data.contents.ptr;
1478 	}
1479 
1480 	ref T opUnary(string op : "*")()
1481 	{
1482 		return *ptr;
1483 	}
1484 }
1485 
1486 /// Ditto, but a dynamic array of values.
1487 struct DataArray(T)
1488 if (!hasIndirections!T)
1489 {
1490 	Data data;
1491 	@property T[] arr()
1492 	{
1493 		assert(data && data.length % T.sizeof == 0);
1494 		return cast(T[])data.contents;
1495 	}
1496 
1497 	T opCast(T : bool)() const
1498 	{
1499 		return !!data;
1500 	}
1501 
1502 	@property T[] toHeap()
1503 	{
1504 		assert(data && data.length % T.sizeof == 0);
1505 		return cast(T[])data.toHeap;
1506 	}
1507 }
1508 
1509 /// Consumes bytes from a Data instance and returns them as typed objects on request.
1510 /// Consumption must be committed explicitly once all desired bytes are read.
1511 struct DataReader
1512 {
1513 	Data data;
1514 
1515 	DataObject!T peek(T)()
1516 	if (!hasIndirections!T)
1517 	{
1518 		if (data.length < T.sizeof)
1519 			return DataObject!T.init;
1520 		return DataObject!T(data[0 .. T.sizeof]);
1521 	}
1522 
1523 	DataArray!T peek(T)(size_t length)
1524 	if (!hasIndirections!T)
1525 	{
1526 		auto size = T.sizeof * length;
1527 		if (data.length < size)
1528 			return DataArray!T.init;
1529 		return DataArray!T(data[0 .. size]);
1530 	}
1531 
1532 	DataObject!T read(T)()
1533 	if (!hasIndirections!T)
1534 	{
1535 		if (auto p = peek!T())
1536 		{
1537 			data = data[T.sizeof .. $];
1538 			return p;
1539 		}
1540 		return DataObject!T.init;
1541 	}
1542 
1543 	DataArray!T read(T)(size_t length)
1544 	if (!hasIndirections!T)
1545 	{
1546 		if (auto p = peek!T(length))
1547 		{
1548 			data = data[T.sizeof * length .. $];
1549 			return p;
1550 		}
1551 		return DataArray!T.init;
1552 	}
1553 }