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