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