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 }