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