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 }