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