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   , simpleDecoder!(xEvent.ClientMessage   )),
1135 		EventSpec!(MappingNotify   , simpleDecoder!(xEvent.MappingNotify   )),
1136 	//	EventSpec!(GenericEvent    , simpleDecoder!(xGenericEvent          )),
1137 	);
1138 
1139 	@property X11Client client() { return this; }
1140 	enum firstEvent = 0;
1141 
1142 	mixin ProtocolGlue;
1143 
1144 	void onConnect()
1145 	{
1146 		xConnClientPrefix prefix;
1147 		version (BigEndian)
1148 			prefix.byteOrder = 'B';
1149 		version (LittleEndian)
1150 			prefix.byteOrder = 'l';
1151 		prefix.majorVersion = X_PROTOCOL;
1152 		prefix.minorVersion = X_PROTOCOL_REVISION;
1153 		prefix.nbytesAuthProto6 = authorizationProtocolName.length.to!CARD16;
1154 		prefix.nbytesAuthString = authorizationProtocolData.length.to!CARD16;
1155 
1156 		conn.send(Data(prefix.asBytes));
1157 		conn.send(pad4(Data(authorizationProtocolName.asBytes)));
1158 		conn.send(pad4(Data(authorizationProtocolData)));
1159 	}
1160 
1161 	Data buffer;
1162 
1163 	bool connected;
1164 
1165 	ushort sequenceNumber = 1;
1166 	void delegate(Data)[] replyHandlers;
1167 
1168 	uint ridCounter; // for newRID
1169 
1170 	void onReadData(Data data)
1171 	{
1172 		buffer ~= data;
1173 
1174 		try
1175 			while (true)
1176 			{
1177 				if (!connected)
1178 				{
1179 					auto reader = DataReader(buffer);
1180 
1181 					auto pConnSetupPrefix = reader.read!xConnSetupPrefix();
1182 					if (!pConnSetupPrefix)
1183 						return;
1184 
1185 					auto additionalBytes = reader.read!uint((*pConnSetupPrefix).length);
1186 					if (!additionalBytes)
1187 						return;
1188 					auto additionalReader = DataReader(additionalBytes.data);
1189 
1190 					connSetupPrefix = *pConnSetupPrefix;
1191 					switch ((*pConnSetupPrefix).success)
1192 					{
1193 						case 0: // Failed
1194 						{
1195 							auto reason = additionalReader.read!char((*pConnSetupPrefix).lengthReason)
1196 								.enforce("Insufficient bytes for reason");
1197 							conn.disconnect("X11 connection failed: " ~ cast(string)reason.arr, DisconnectType.error);
1198 							break;
1199 						}
1200 						case 1: // Success
1201 						{
1202 							auto pConnSetup = additionalReader.read!xConnSetup()
1203 								.enforce("Connection setup packet too short");
1204 							connSetup = *pConnSetup;
1205 
1206 							auto vendorBytes = additionalReader.read!uint(((*pConnSetup).nbytesVendor + 3) / 4)
1207 								.enforce("Connection setup packet too short");
1208 							this.vendor = DataReader(vendorBytes.data).read!char((*pConnSetup).nbytesVendor).arr.idup;
1209 
1210 							// scope(failure) { import std.stdio, ae.utils.json; writeln(connSetupPrefix.toPrettyJson); writeln(connSetup.toPrettyJson); writeln(pixmapFormats.toPrettyJson); writeln(roots.toPrettyJson); }
1211 
1212 							this.pixmapFormats =
1213 								additionalReader.read!xPixmapFormat((*pConnSetup).numFormats)
1214 								.enforce("Connection setup packet too short")
1215 								.arr.idup;
1216 							foreach (i; 0 .. (*pConnSetup).numRoots)
1217 							{
1218 								Root root;
1219 								// scope(failure) { import std.stdio, ae.utils.json; writeln(root.toPrettyJson); }
1220 								root.root = *additionalReader.read!xWindowRoot()
1221 									.enforce("Connection setup packet too short");
1222 								foreach (j; 0 .. root.root.nDepths)
1223 								{
1224 									Root.Depth depth;
1225 									depth.depth = *additionalReader.read!xDepth()
1226 										.enforce("Connection setup packet too short");
1227 									depth.visualTypes = additionalReader.read!xVisualType(depth.depth.nVisuals)
1228 										.enforce("Connection setup packet too short")
1229 										.arr.idup;
1230 									root.depths ~= depth;
1231 								}
1232 								this.roots ~= root;
1233 							}
1234 
1235 							enforce(!additionalReader.data.length,
1236 								"Left-over bytes in connection setup packet");
1237 
1238 							connected = true;
1239 							if (handleConnect)
1240 								handleConnect();
1241 
1242 							break;
1243 						}
1244 						case 2: // Authenticate
1245 						{
1246 							auto reason = additionalReader.read!char((*pConnSetupPrefix).lengthReason)
1247 								.enforce("Insufficient bytes for reason");
1248 							conn.disconnect("X11 authentication required: " ~ cast(string)reason.arr, DisconnectType.error);
1249 							break;
1250 						}
1251 						default:
1252 							throw new Exception("Unknown connection success code");
1253 					}
1254 
1255 					buffer = reader.data;
1256 				}
1257 
1258 				if (connected)
1259 				{
1260 					auto reader = DataReader(buffer);
1261 
1262 					auto pGenericReply = reader.peek!xGenericReply();
1263 					if (!pGenericReply)
1264 						return;
1265 
1266 					Data packet;
1267 
1268 					switch ((*pGenericReply).type)
1269 					{
1270 						case X_Error:
1271 						default:
1272 							packet = reader.read!xGenericReply().data;
1273 							assert(packet);
1274 							break;
1275 						case X_Reply:
1276 						case GenericEvent:
1277 							packet = reader.read!uint(8 + (*pGenericReply).length).data;
1278 							if (!packet)
1279 								return;
1280 							break;
1281 					}
1282 
1283 					switch ((*pGenericReply).type)
1284 					{
1285 						case X_Error:
1286 							if (handleError)
1287 								handleError(*DataReader(packet).read!xError);
1288 							else
1289 							{
1290 								debug (X11) stderr.writeln(*DataReader(packet).read!xError);
1291 								throw new Exception("Protocol error");
1292 							}
1293 							break;
1294 						case X_Reply:
1295 							onReply(packet);
1296 							break;
1297 						case GenericEvent:
1298 							if (handleGenericEvent)
1299 								handleGenericEvent(packet);
1300 							break;
1301 						default:
1302 							onEvent(packet);
1303 					}
1304 
1305 					buffer = reader.data;
1306 				}
1307 			}
1308 		catch (CaughtException e)
1309 			if (conn.state.disconnectable)
1310 				conn.disconnect(e.msg, DisconnectType.error);
1311 	}
1312 
1313 	void onReply(Data packet)
1314 	{
1315 		auto pHeader = DataReader(packet).peek!xGenericReply;
1316 		auto handler = replyHandlers[(*pHeader).sequenceNumber];
1317 		enforce(handler !is null,
1318 			"Unexpected packet");
1319 		replyHandlers[(*pHeader).sequenceNumber] = null;
1320 		handler(packet);
1321 	}
1322 
1323 	void onEvent(Data packet)
1324 	{
1325 		auto pEvent = DataReader(packet).peek!xEvent;
1326 		auto eventType = (*pEvent).u.type & 0x7F;
1327 		// bool artificial = !!((*pEvent).u.type >> 7);
1328 		auto handler = eventHandlers[eventType];
1329 		if (!handler)
1330 			throw new Exception("Unrecognized event: " ~ eventType.to!string);
1331 		return handler(packet);
1332 	}
1333 
1334 	/*private*/ public void sendRequest(BYTE reqType, Data requestData, void delegate(Data) handler)
1335 	{
1336 		assert(requestData.length >= sz_xReq);
1337 		assert(requestData.length % 4 == 0);
1338 		requestData.enter((scope contents) {
1339 			auto pReq = cast(xReq*)contents.ptr;
1340 			pReq.reqType = reqType;
1341 			pReq.length = (requestData.length / 4).to!ushort;
1342 		});
1343 
1344 		enforce(replyHandlers[sequenceNumber] is null,
1345 			"Sequence number overflow"); // We haven't yet received a reply from the previous cycle
1346 		replyHandlers[sequenceNumber] = handler;
1347 		conn.send(requestData);
1348 		sequenceNumber++;
1349 	}
1350 }
1351 
1352 // ************************************************************************
1353 
1354 /// Base class for X11 extensions.
1355 abstract class X11Extension : X11SubProtocol
1356 {
1357 	X11Client client;
1358 	CARD8 majorOpcode, firstEvent, firstError;
1359 
1360 	this(X11Client client, ReturnType!(simpleDecoder!xQueryExtensionReply) reply)
1361 	{
1362 		this.client = client;
1363 		this.majorOpcode = reply.major_opcode;
1364 		this.firstEvent = reply.first_event;
1365 		this.firstError = reply.first_error;
1366 	}
1367 }
1368 
1369 /// Example extension:
1370 version (none)
1371 class XEXAMPLE : X11Extension
1372 {
1373 	/// Mix this in to generate sendXXX and handleXXX declarations.
1374 	mixin ProtocolGlue;
1375 
1376 	/// The name by which to request the extension (as in X_QueryExtension).
1377 	enum name = XEXAMPLE_NAME;
1378 	/// The extension's base request type.
1379 	alias BaseReq = xXExampleReq;
1380 	/// The name of the field encoding the extension's opcode.
1381 	enum reqTypeFieldName = q{xexampleReqType};
1382 
1383 	/// Declare the extension's requests here.
1384 	alias RequestSpecs = AliasSeq!(
1385 		RequestSpec!(
1386 			X_XExampleRequest,
1387 			simpleExtEncoder!xXExampleRequestReq,
1388 			simpleDecoder!xXExampleRequestReply,
1389 		),
1390 	);
1391 
1392 	/// Declare the extension's events here.
1393 	alias EventSpecs = AliasSeq!(
1394 		EventSpec!(XExampleNotify, simpleDecoder!xXExampleNotifyEvent),
1395 	);
1396 }
1397 
1398 // ************************************************************************
1399 
1400 private:
1401 
1402 mixin template Optionals(args...)
1403 if (args.length % 2 == 0)
1404 {
1405 	alias _args = args; // DMD bug workaround
1406 
1407 	private mixin template Field(size_t i)
1408 	{
1409 		alias Type = args[i * 2 + 1];
1410 		enum maskName = __traits(identifier, args[i * 2]);
1411 		enum fieldName = toLower(maskName[2]) ~ maskName[3 .. $];
1412 		enum prefix = is(typeof(mixin("(){int " ~ fieldName ~ "; }()"))) ? "" : "c_";
1413 		mixin(`Nullable!Type ` ~ prefix ~ fieldName ~ ';');
1414 	}
1415 
1416 	static foreach (i; 0 .. args.length / 2)
1417 		mixin Field!i;
1418 
1419 	CARD32[] _serialize(Mask)(ref Mask mask) const
1420 	{
1421 		CARD32[] result;
1422 		static foreach (i; 0 .. _args.length / 2)
1423 			if (!this.tupleof[i].isNull)
1424 			{
1425 				enum fieldMask = _args[i * 2];
1426 				assert(mask < fieldMask);
1427 				result ~= this.tupleof[i].get();
1428 				mask |= fieldMask;
1429 			}
1430 		return result;
1431 	}
1432 }
1433 
1434 /// Generate code to populate all of a request struct's fields from arguments / locals.
1435 string populateRequestFromLocals(T)()
1436 {
1437 	string code = T.stringof ~ " req;\n";
1438 	foreach (i; rangeTuple!(T.tupleof.length))
1439 	{
1440 		enum name = __traits(identifier, T.tupleof[i]);
1441 		enum isPertinentField =
1442 			name != "reqType" &&
1443 			name != "length" &&
1444 			(name.length < 3 || name[0..min($, 3)] != "pad");
1445 		if (isPertinentField)
1446 		{
1447 			code ~= "req." ~ name ~ " = " ~ name ~ ";\n";
1448 			debug(X11) code ~= `stderr.writeln("[X11] >> ` ~ name ~ `: ", ` ~ name ~ `);`;
1449 		}
1450 	}
1451 	return code;
1452 }
1453 
1454 // ************************************************************************
1455 
1456 // TODO: these are unsafe; migrate to something on top of the safe Data API
1457 
1458 /// Typed wrapper for Data.
1459 /// Because Data is reference counted, this type allows encapsulating
1460 /// a safe but typed reference to a Data slice.
1461 struct DataObject(T)
1462 if (!hasIndirections!T)
1463 {
1464 	Data data;
1465 
1466 	T opCast(T : bool)() const
1467 	{
1468 		return !!data;
1469 	}
1470 
1471 	@property T* ptr()
1472 	{
1473 		assert(data && data.length == T.sizeof);
1474 		return cast(T*)data.unsafeContents.ptr;
1475 	}
1476 
1477 	ref T opUnary(string op : "*")()
1478 	{
1479 		return *ptr;
1480 	}
1481 }
1482 
1483 /// Ditto, but a dynamic array of values.
1484 struct DataArray(T)
1485 if (!hasIndirections!T)
1486 {
1487 	Data data;
1488 	@property T[] arr()
1489 	{
1490 		assert(data && data.length % T.sizeof == 0);
1491 		return cast(T[])data.unsafeContents;
1492 	}
1493 
1494 	T opCast(T : bool)() const
1495 	{
1496 		return !!data;
1497 	}
1498 
1499 	@property T[] toGC()
1500 	{
1501 		assert(data && data.length % T.sizeof == 0);
1502 		return cast(T[])data.toGC();
1503 	}
1504 }
1505 
1506 /// Consumes bytes from a Data instance and returns them as typed objects on request.
1507 /// Consumption must be committed explicitly once all desired bytes are read.
1508 struct DataReader
1509 {
1510 	Data data;
1511 
1512 	DataObject!T peek(T)()
1513 	if (!hasIndirections!T)
1514 	{
1515 		if (data.length < T.sizeof)
1516 			return DataObject!T.init;
1517 		return DataObject!T(data[0 .. T.sizeof]);
1518 	}
1519 
1520 	DataArray!T peek(T)(size_t length)
1521 	if (!hasIndirections!T)
1522 	{
1523 		auto size = T.sizeof * length;
1524 		if (data.length < size)
1525 			return DataArray!T.init;
1526 		return DataArray!T(data[0 .. size]);
1527 	}
1528 
1529 	DataObject!T read(T)()
1530 	if (!hasIndirections!T)
1531 	{
1532 		if (auto p = peek!T())
1533 		{
1534 			data = data[T.sizeof .. $];
1535 			return p;
1536 		}
1537 		return DataObject!T.init;
1538 	}
1539 
1540 	DataArray!T read(T)(size_t length)
1541 	if (!hasIndirections!T)
1542 	{
1543 		if (auto p = peek!T(length))
1544 		{
1545 			data = data[T.sizeof * length .. $];
1546 			return p;
1547 		}
1548 		return DataArray!T.init;
1549 	}
1550 }