1 // Copyright © 2017-2021 Rémi Thebault 2 /++ 3 + Wayland scanner for D. 4 + Generation of server protocol code. 5 +/ 6 module wayland.scanner.server; 7 8 import wayland.scanner; 9 import wayland.scanner.common; 10 11 import arsd.dom; 12 13 import std.algorithm; 14 import std.format; 15 import std.range; 16 17 alias Interface = wayland.scanner.common.Interface; 18 19 class ServerFactory : Factory 20 { 21 override Protocol makeProtocol(Element el) 22 { 23 return new ServerProtocol(el); 24 } 25 override Interface makeInterface(Element el, Protocol protocol) 26 { 27 return new ServerInterface(el, protocol); 28 } 29 override Message makeMessage(Element el, Interface iface) 30 { 31 return new ServerMessage(el, iface); 32 } 33 override Arg makeArg(Element el) 34 { 35 return new ServerArg(el); 36 } 37 } 38 39 40 // Server bindings implementation notes: 41 // There are two kind of server objects: globals and resources. 42 // 43 // Globals inherit WlGlobal and are to be created by the compositor at startup. 44 // Each created global is announced by the registry to the client. 45 // `[Global].bind` function create a `[Global].Resource` object whose implementation 46 // is set automatically to the outer Global object, which MUST override the abstract 47 // request handlers. 48 // 49 // Resources inherit WlResource. They are created by global or other parent resource 50 // objects upon client requests. These resources must be subclassed by the application 51 // and their abstract request handlers must be overriden. 52 53 54 class ServerArg : Arg 55 { 56 this(Element el) 57 { 58 super(el); 59 } 60 61 override @property string dType() const 62 { 63 final switch(type) { 64 case ArgType.Int: 65 case ArgType.UInt: 66 case ArgType.Fixed: 67 case ArgType.String: 68 case ArgType.Array: 69 case ArgType.Fd: 70 return Arg.dType; 71 case ArgType.NewId: 72 return "uint"; 73 case ArgType.Object: 74 if (iface.length) 75 { 76 auto i = ServerInterface.get(iface); 77 if (i && i.isGlobal) { 78 return i.dName ~ ".Resource"; 79 } 80 else if (i && !i.isGlobal) { 81 return i.dName; 82 } 83 } 84 return "WlResource"; 85 } 86 } 87 88 override @property string cType() const 89 { 90 final switch(type) { 91 case ArgType.Int: 92 case ArgType.UInt: 93 case ArgType.Fixed: 94 case ArgType.String: 95 case ArgType.Array: 96 case ArgType.Fd: 97 case ArgType.Object: 98 return Arg.cType; 99 case ArgType.NewId: 100 return "uint"; 101 } 102 } 103 104 override @property string cCastExpr() const 105 { 106 final switch(type) { 107 case ArgType.Int: 108 case ArgType.UInt: 109 case ArgType.Fixed: 110 case ArgType.String: 111 case ArgType.NewId: 112 case ArgType.Array: 113 case ArgType.Fd: 114 return Arg.cCastExpr; 115 case ArgType.Object: 116 return format("%s.native", paramName); 117 } 118 } 119 } 120 121 122 class ServerMessage : Message 123 { 124 this (Element el, Interface iface) 125 { 126 super(el, iface); 127 } 128 129 @property ServerInterface svIface() 130 { 131 return cast(ServerInterface)iface; 132 } 133 134 @property auto svArgs() 135 { 136 return args.map!(a => cast(ServerArg)a); 137 } 138 139 @property string reqMethodName() 140 { 141 return camelName(name); 142 } 143 144 @property string sendName() 145 { 146 return "send" ~ titleCamelName(name); 147 } 148 149 @property string privRqListenerStubName() 150 { 151 return format("wl_d_%s_%s", ifaceName, name); 152 } 153 154 @property string[] reqRtArgs() 155 { 156 string[] rtArgs = [ "WlClient cl" ]; 157 if (iface.isGlobal) rtArgs ~= format( 158 "%s res", svIface.selfResType(Yes.local) 159 ); 160 foreach (a; args) { 161 if (a.type == ArgType.NewId && !a.iface.length) 162 { 163 rtArgs ~= [ 164 "string iface", "uint ver", format("uint %s", a.paramName) 165 ]; 166 } 167 else { 168 rtArgs ~= format("%s %s", a.dType, a.paramName); 169 } 170 } 171 return rtArgs; 172 } 173 174 @property string reqRetStr() 175 { 176 final switch(reqType) 177 { 178 case ReqType.newObj: 179 return ifaceDName(reqRet.iface); 180 case ReqType.dynObj: 181 return "WlResource"; 182 case ReqType.void_: 183 return "void"; 184 } 185 } 186 187 void writeReqMethodDecl(SourceFile sf, string[] attrs) 188 { 189 writeFnSigRaw(sf, attrs.join(" "), reqRetStr, reqMethodName, reqRtArgs); 190 sf.writeln(";"); 191 } 192 193 194 void writeSendResMethod(SourceFile sf) 195 { 196 description.writeCode(sf); 197 string[] rtArgs; 198 string[] exprs = [ 199 "this.native", 200 opCodeName, 201 ]; 202 foreach (arg; args) 203 { 204 rtArgs ~= format("%s %s", arg.dType, arg.paramName); 205 exprs ~= arg.cCastExpr; 206 } 207 sf.writeFnSig("void", sendName, rtArgs); 208 sf.writeFnBody([], "wl_resource_post_event", exprs, []); 209 } 210 211 void writePrivRqListenerStub(SourceFile sf) 212 { 213 string[] rtArgs = [ 214 "wl_client* natCl", "wl_resource* natRes", 215 ]; 216 string[] exprs = [ 217 "cast(WlClient)ObjectCache.get(natCl)", 218 ]; 219 if (iface.isGlobal) exprs ~= "_res"; 220 foreach (a; args) { 221 if (a.type == ArgType.Object) 222 { 223 rtArgs ~= format("wl_resource* %s", a.paramName); 224 // TODO: check if wl_resource_get_user_data could work here 225 exprs ~= format("cast(%s)ObjectCache.get(%s)", a.dType, a.paramName); 226 } 227 else if (a.type == ArgType.NewId && !a.iface.length) 228 { 229 rtArgs ~= [ 230 "const(char)* iface", "uint ver", format("uint %s", a.paramName) 231 ]; 232 exprs ~= [ 233 "fromStringz(iface).idup", "ver", a.paramName 234 ]; 235 } 236 else { 237 rtArgs ~= format("%s %s", a.cType, a.paramName); 238 exprs ~= a.dCastExpr(ifaceName); 239 } 240 } 241 writeFnSig(sf, "void", privRqListenerStubName, rtArgs); 242 sf.bracedBlock!({ 243 sf.writeln("nothrowFnWrapper!({"); 244 sf.indentedBlock!({ 245 immutable resType = svIface.selfResType(No.local); 246 sf.writeln("auto _res = cast(%s)wl_resource_get_user_data(natRes);", resType); 247 immutable outer = iface.isGlobal ? ".outer" : ""; 248 writeFnExpr(sf, format("_res%s.%s", outer, reqMethodName), exprs); 249 }); 250 sf.writeln("});"); 251 }); 252 } 253 254 void writePrivStubSig(SourceFile sf) 255 { 256 string[] rtArgs = [ 257 "wl_client* natCl", "wl_resource* natRes", 258 ]; 259 foreach (a; args) { 260 if (a.type == ArgType.Object) 261 { 262 rtArgs ~= format("wl_resource* %s", a.paramName); 263 } 264 else if (a.type == ArgType.NewId && !a.iface.length) 265 { 266 rtArgs ~= [ 267 "const(char)* iface", "uint ver", format("uint %s", a.paramName) 268 ]; 269 } 270 else { 271 rtArgs ~= format("%s %s", a.cType, a.paramName); 272 } 273 } 274 writeFnPointer(sf, name, "void", rtArgs); 275 } 276 } 277 278 279 class ServerInterface : Interface 280 { 281 282 283 static ServerInterface get(string name) 284 { 285 auto i = Interface.get(name); 286 if (i) return cast(ServerInterface)i; 287 else return null; 288 } 289 290 291 this (Element el, Protocol protocol) 292 { 293 super(el, protocol); 294 } 295 296 @property auto svRequests() 297 { 298 return requests.map!(m => cast(ServerMessage)m); 299 } 300 301 @property auto svEvents() 302 { 303 return events.map!(m => cast(ServerMessage)m); 304 } 305 306 string selfResType(Flag!"local" local) 307 { 308 if (local) { 309 return isGlobal ? "Resource" : dName; 310 } 311 else { 312 return dName ~ (isGlobal ? ".Resource" : ""); 313 } 314 } 315 316 @property string bindFuncName() 317 { 318 return format("wl_d_bind_%s", name); 319 } 320 321 @property string listenerStubsStructName() 322 { 323 return format("%sListenersAggregate", titleCamelName(name)); 324 } 325 326 @property string listenerStubsSymbol() 327 { 328 return format("%sListeners", camelName(name)); 329 } 330 331 override void writeCode(SourceFile sf) 332 { 333 description.writeCode(sf); 334 immutable heritage = name == "wl_display" ? " : WlDisplayBase" : 335 (isGlobal ? " : WlGlobal" : " : WlResource"); 336 immutable attrs = name == "wl_display" ? "" : 337 (requests.length ? "abstract " : ""); 338 sf.writeln("%sclass %s%s", attrs, dName, heritage); 339 sf.bracedBlock!({ 340 writeVersion(sf); 341 sf.writeln(); 342 if (name == "wl_display") 343 { 344 sf.writeln("this(wl_display* native)"); 345 sf.bracedBlock!({ 346 sf.writeln("super(native);"); 347 }); 348 sf.writeln(); 349 } 350 351 writeIfaceAccess(sf); 352 353 writeConstants(sf); 354 355 foreach (en; enums) 356 { 357 sf.writeln(); 358 en.writeCode(sf); 359 } 360 361 if (name != "wl_display") 362 { 363 if (isGlobal) 364 { 365 writeGlobalCode(sf); 366 } 367 else 368 { 369 writeResourceCode(sf); 370 } 371 } 372 }); 373 } 374 375 void writeIfaceAccess(SourceFile sf) 376 { 377 sf.writeln("/// Access to the interface of \"%s.%s\"", protocol.name, name); 378 sf.writeln("static @property immutable(WlInterface) iface()"); 379 sf.bracedBlock!({ 380 sf.writeln("return %sIface;", camelName(name)); 381 }); 382 } 383 384 void writeConstants(SourceFile sf) 385 { 386 if (events.length) 387 { 388 sf.writeln(); 389 foreach(i, msg; events) 390 { 391 sf.writeln("/// Op-code of %s.%s.", name, msg.name); 392 sf.writeln("enum %s = %d;", msg.opCodeName, i); 393 } 394 sf.writeln(); 395 foreach(msg; events) 396 { 397 sf.writeln( 398 "/// Version of %s protocol introducing %s.%s.", 399 protocol.name, name, msg.name 400 ); 401 sf.writeln("enum %sSinceVersion = %d;", camelName(msg.name), msg.since); 402 } 403 } 404 if (requests.length) 405 { 406 sf.writeln(); 407 foreach(msg; requests) 408 { 409 sf.writeln( 410 "/// %s protocol version introducing %s.%s.", 411 protocol.name, name, msg.name 412 ); 413 sf.writeln("enum %sSinceVersion = %d;", camelName(msg.name), msg.since); 414 } 415 } 416 } 417 418 void writeGlobalCode(SourceFile sf) 419 { 420 sf.writeln(); 421 sf.writeln("protected this(WlDisplay dpy, uint ver)"); 422 sf.bracedBlock!({ 423 sf.writeln("super(wl_global_create("); 424 sf.indentedBlock!({ 425 sf.writeln("dpy.native, iface.native, ver, cast(void*)this, &%s", bindFuncName); 426 }); 427 sf.writeln("));"); 428 }); 429 sf.writeln(); 430 sf.writeln("/// Create a Resource object when a client connects."); 431 sf.writeln("Resource bind(WlClient cl, uint ver, uint id)"); 432 sf.bracedBlock!({ 433 sf.writeln("return new Resource(cl, ver, id);"); 434 }); 435 foreach(rq; svRequests) 436 { 437 sf.writeln(); 438 rq.description.writeCode(sf); 439 rq.writeReqMethodDecl(sf, ["abstract", "protected"]); 440 } 441 sf.writeln(); 442 writeResourceCodeForGlobal(sf); 443 } 444 445 void writeResourceCodeForGlobal(SourceFile sf) 446 { 447 sf.writeln("class Resource : WlResource"); 448 sf.bracedBlock!({ 449 writeResourceCtors(sf, []); 450 foreach(ev; svEvents) 451 { 452 sf.writeln(); 453 ev.writeSendResMethod(sf); 454 } 455 }); 456 } 457 458 void writeResourceCode(SourceFile sf) 459 { 460 sf.writeln(); 461 writeResourceCtors(sf, ["protected"]); 462 foreach(rq; svRequests) 463 { 464 sf.writeln(); 465 rq.description.writeCode(sf); 466 rq.writeReqMethodDecl(sf, ["abstract", "protected"]); 467 } 468 foreach(ev; svEvents) 469 { 470 sf.writeln(); 471 ev.writeSendResMethod(sf); 472 } 473 } 474 475 void writeResourceCtors(SourceFile sf, string[] attrs) 476 { 477 immutable attrStr = attrs.join(" ") ~ (attrs.length ? " " : ""); 478 sf.writeln("%sthis(WlClient cl, uint ver, uint id)", attrStr); 479 sf.bracedBlock!({ 480 immutable natExpr = "wl_resource_create(cl.native, iface.native, ver, id)"; 481 if (requests.length) 482 { 483 sf.writeln("auto native = %s;", natExpr); 484 writeFnExpr(sf, "wl_resource_set_implementation", [ 485 "native", format("&%s", listenerStubsSymbol), 486 "cast(void*)this", "null" 487 ]); 488 sf.writeln("super(native);"); 489 } 490 else 491 { 492 sf.writeln("super(%s);", natExpr); 493 } 494 }); 495 sf.writeln(); 496 sf.writeln("%sthis(wl_resource* natRes)", attrStr); 497 sf.bracedBlock!({ 498 sf.writeln("super(natRes);"); 499 }); 500 } 501 502 void writePrivBindStub(SourceFile sf) 503 { 504 sf.writeln("void %s(wl_client* natCl, void* data, uint ver, uint id)", bindFuncName); 505 sf.bracedBlock!({ 506 sf.writeln("nothrowFnWrapper!({"); 507 sf.indentedBlock!({ 508 sf.writeln("auto g = cast(%s)data;", dName); 509 sf.writeln("auto cl = cast(WlClient)ObjectCache.get(natCl);"); 510 sf.writeln(`assert(g && cl, "%s: could not get global or client from cache");`, bindFuncName); 511 sf.writeln("g.bind(cl, ver, id);"); 512 }); 513 sf.writeln("});"); 514 }); 515 } 516 517 void writePrivRqListenerStubs(SourceFile sf) 518 { 519 sf.writeln("// %s listener stubs", name); 520 foreach(rq; svRequests) 521 { 522 sf.writeln(); 523 rq.writePrivRqListenerStub(sf); 524 } 525 526 sf.writeln(); 527 sf.writeln("struct %s", listenerStubsStructName); 528 sf.bracedBlock!({ 529 foreach (rq; svRequests) { 530 rq.writePrivStubSig(sf); 531 } 532 }); 533 } 534 535 void writePrivRqListenerStubsSymbol(SourceFile sf) 536 { 537 sf.writeln("__gshared %s = %s (", listenerStubsSymbol, listenerStubsStructName); 538 sf.indentedBlock!({ 539 foreach(rq; svRequests) { 540 sf.writeln("&%s,", rq.privRqListenerStubName); 541 } 542 }); 543 sf.writeln(");"); 544 } 545 } 546 547 class ServerProtocol : Protocol 548 { 549 this(Element el) 550 { 551 super(el); 552 } 553 554 @property auto svIfaces() 555 { 556 return ifaces.map!(i => cast(ServerInterface)i); 557 } 558 559 @property auto svGlobalIfaces() 560 { 561 return svIfaces.filter!(i => i.isGlobal); 562 } 563 564 @property auto svIfacesWithRq() 565 { 566 return svIfaces.filter!(i => i.requests.length > 0); 567 } 568 569 override void writeCode(SourceFile sf, in Options opt) 570 { 571 writeHeader(sf, opt); 572 if (name == "wayland") sf.writeln("import wayland.server.core;"); 573 else sf.writeln("import wayland.server;"); 574 sf.writeln("import wayland.native.server;"); 575 sf.writeln("import wayland.native.util;"); 576 sf.writeln("import wayland.util;"); 577 sf.writeln("import std.string : toStringz, fromStringz;"); 578 sf.writeln(); 579 580 foreach(iface; ifaces) 581 { 582 iface.writeCode(sf); 583 sf.writeln(); 584 } 585 586 // writing private code 587 sf.writeln("private:"); 588 sf.writeln(); 589 590 writePrivIfaces(sf); 591 sf.writeln(); 592 593 sf.writeln("extern(C) nothrow"); 594 sf.bracedBlock!({ 595 bool needNL; 596 foreach(iface; svGlobalIfaces.filter!(i=>i.name != "wl_display")) 597 { 598 if (needNL) sf.writeln(); 599 else needNL = true; 600 iface.writePrivBindStub(sf); 601 } 602 603 foreach(iface; svIfacesWithRq.filter!(i=>i.name != "wl_display")) 604 { 605 if (needNL) sf.writeln(); 606 else needNL = true; 607 iface.writePrivRqListenerStubs(sf); 608 } 609 }); 610 foreach(iface; svIfacesWithRq.filter!(i=>i.name != "wl_display")) 611 { 612 sf.writeln(); 613 iface.writePrivRqListenerStubsSymbol(sf); 614 } 615 } 616 617 override void writePrivIfaces(SourceFile sf) 618 { 619 foreach(iface; ifaces) 620 { 621 sf.writeln("immutable WlInterface %sIface;", camelName(iface.name)); 622 } 623 624 writeNativeIfaces(sf); 625 } 626 627 override void writeNativeIfacesAssignment(SourceFile sf) 628 { 629 foreach (iface; ifaces) 630 { 631 sf.writeln("%sIface = new immutable WlInterface ( &wl_ifaces[%s] );", 632 camelName(iface.name), indexSymbol(iface.name) 633 ); 634 } 635 } 636 }