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