1 // Copyright © 2017-2021 Rémi Thebault
2 /++
3  +  Wayland scanner for D.
4  +  Generation of client protocol code.
5  +/
6 module wayland.scanner.client;
7 
8 import wayland.scanner;
9 import wayland.scanner.common;
10 
11 import arsd.dom;
12 
13 import std.algorithm;
14 import std.exception;
15 import std.format;
16 import std.range;
17 
18 
19 alias Interface = wayland.scanner.common.Interface;
20 
21 class ClientFactory : Factory
22 {
23     override Protocol makeProtocol(Element el)
24     {
25         return new ClientProtocol(el);
26     }
27     override Interface makeInterface(Element el, Protocol protocol)
28     {
29         return new ClientInterface(el, protocol);
30     }
31     override Message makeMessage(Element el, Interface iface)
32     {
33         return new ClientMessage(el, iface);
34     }
35     override Arg makeArg(Element el)
36     {
37         return new ClientArg(el);
38     }
39 }
40 
41 class ClientArg : Arg
42 {
43     this(Element el)
44     {
45         super(el);
46     }
47 
48     override @property string dType() const
49     {
50         final switch(type) {
51             case ArgType.Int:
52             case ArgType.UInt:
53             case ArgType.Fixed:
54             case ArgType.String:
55             case ArgType.Array:
56             case ArgType.Fd:
57                 return Arg.dType;
58             case ArgType.NewId:
59             case ArgType.Object:
60                 if (iface.length)
61                     return ifaceDName(iface);
62                 else
63                     return "WlProxy";
64         }
65     }
66 
67     override @property string cType() const
68     {
69         final switch(type) {
70             case ArgType.Int:
71             case ArgType.UInt:
72             case ArgType.Fixed:
73             case ArgType.String:
74             case ArgType.Array:
75             case ArgType.Fd:
76             case ArgType.Object:
77                 return Arg.cType;
78             case ArgType.NewId:
79                 return "wl_proxy*";
80         }
81     }
82 
83     override @property string cCastExpr() const
84     {
85         final switch(type) {
86             case ArgType.Int:
87             case ArgType.UInt:
88             case ArgType.Fixed:
89             case ArgType.String:
90             case ArgType.Array:
91             case ArgType.Fd:
92                 return Arg.cCastExpr;
93             case ArgType.NewId:
94             case ArgType.Object:
95                 return format("%s.proxy", paramName);
96         }
97     }
98 
99     @property string reqCType() const
100     {
101         final switch(type) {
102             case ArgType.Int:
103             case ArgType.UInt:
104             case ArgType.Fixed:
105             case ArgType.String:
106             case ArgType.Array:
107             case ArgType.Fd:
108                 return Arg.cType;
109             case ArgType.NewId:
110             case ArgType.Object:
111                 return "wl_proxy*";
112         }
113     }
114 
115     @property string evCType() const
116     {
117         final switch(type) {
118             case ArgType.Int:
119             case ArgType.UInt:
120             case ArgType.Fixed:
121             case ArgType.String:
122             case ArgType.Array:
123             case ArgType.Fd:
124                 return Arg.cType;
125             case ArgType.NewId:
126             case ArgType.Object:
127                 if (iface.empty) return "void*";
128                 else return "wl_proxy*";
129         }
130     }
131 
132     override string dCastExpr(string parentIface) const
133     {
134         final switch(type) {
135             case ArgType.Int:
136             case ArgType.UInt:
137             case ArgType.Fixed:
138             case ArgType.String:
139             case ArgType.Array:
140             case ArgType.Fd:
141                 return Arg.dCastExpr(parentIface);
142             case ArgType.NewId:
143                 if (iface)
144                     return format("new %s(%s)", ifaceDName(iface), paramName);
145                 else
146                     return format("WlProxy.get(%s)", paramName);
147             case ArgType.Object:
148                 auto expr = format("WlProxy.get(%s)", paramName);
149                 if (iface)
150                     return format("cast(%s)%s", ifaceDName(iface), expr);
151                 else
152                     return expr;
153         }
154     }
155 }
156 
157 
158 class ClientMessage : Message
159 {
160     this (Element el, Interface iface)
161     {
162         super(el, iface);
163     }
164 
165     @property auto clArgs()
166     {
167         return args.map!(a => cast(ClientArg)a);
168     }
169 
170     @property string reqRetStr()
171     {
172         final switch (reqType)
173         {
174         case ReqType.newObj:
175             return ifaceDName(reqRet.iface);
176         case ReqType.dynObj:
177             return "WlProxy";
178         case ReqType.void_:
179             return "void";
180         }
181     }
182 
183     void writeRequestCode(SourceFile sf)
184     {
185         final switch(reqType)
186         {
187         case ReqType.void_:
188             writeVoidReqDefinitionCode(sf);
189             break;
190         case ReqType.newObj:
191             writeNewObjReqDefinitionCode(sf);
192             break;
193         case ReqType.dynObj:
194             writeDynObjReqDefinitionCode(sf);
195             break;
196         }
197     }
198 
199     void writeEventDgAlias(SourceFile sf)
200     {
201         sf.writeln("/// Event delegate signature of %s.%s.", ifaceDName(ifaceName), dHandlerName);
202         string[] rtArgs = [
203             format("%s %s", ifaceDName(ifaceName), camelName(ifaceName))
204         ];
205         foreach(a; args)
206         {
207             rtArgs ~= format("%s %s", a.dType, a.paramName);
208         }
209         writeDelegateAlias(sf, dEvDgType, "void", rtArgs);
210     }
211 
212     void writeEventDgAccessor(SourceFile sf)
213     {
214         description.writeCode(sf);
215         sf.writeln("@property void %s(%s dg)", dHandlerName, dEvDgType);
216         sf.bracedBlock!({
217             sf.writeln("_%s = dg;", dHandlerName);
218         });
219     }
220 
221     void writeVoidReqDefinitionCode(SourceFile sf)
222     {
223         string[] rtArgs;
224         string[] exprs = [
225             "proxy", opCodeName
226         ];
227         foreach(arg; args)
228         {
229             rtArgs ~= (arg.dType ~ " " ~ arg.paramName);
230             exprs ~= arg.cCastExpr;
231         }
232         string[] postStmt = isDtor ? ["super.destroyNotify();"] : [];
233 
234         description.writeCode(sf);
235         sf.writeFnSig("void", dMethodName, rtArgs);
236         sf.writeFnBody([], "wl_proxy_marshal", exprs, postStmt);
237     }
238 
239     void writeNewObjReqDefinitionCode(SourceFile sf)
240     {
241         string[] rtArgs;
242         string[] exprs = [
243             "proxy", opCodeName, format("%s.iface.native", ifaceDName(reqRet.iface))
244         ];
245         foreach(arg; args)
246         {
247             if (arg is reqRet)
248             {
249                 exprs ~= "null";
250             }
251             else
252             {
253                 rtArgs ~= format("%s %s", arg.dType, arg.paramName);
254                 exprs ~= arg.cCastExpr;
255             }
256         }
257         description.writeCode(sf);
258         sf.writeFnSig(reqRetStr, dMethodName, rtArgs);
259         sf.writeFnBody([],
260             "auto _pp = wl_proxy_marshal_constructor", exprs,
261             [   "if (!_pp) return null;",
262                 "auto _p = WlProxy.get(_pp);",
263                 format("if (_p) return cast(%s)_p;", reqRetStr),
264                 format("return new %s(_pp);", reqRetStr)    ]
265         );
266     }
267 
268     void writeDynObjReqDefinitionCode(SourceFile sf)
269     {
270         string[] rtArgs;
271         string[] exprs = [
272             "proxy", opCodeName, "iface.native", "ver"
273         ];
274         foreach(arg; args)
275         {
276             if (arg is reqRet)
277             {
278                 rtArgs ~= [ "immutable(WlProxyInterface) iface", "uint ver" ];
279                 exprs ~= [ "iface.native.name", "ver" ];
280             }
281             else
282             {
283                 rtArgs ~= format("%s %s", arg.dType, arg.paramName);
284                 exprs ~= arg.cCastExpr;
285             }
286         }
287         description.writeCode(sf);
288         sf.writeFnSig(reqRetStr, dMethodName, rtArgs);
289         sf.writeFnBody([],
290             "auto _pp = wl_proxy_marshal_constructor_versioned", exprs,
291             [   "if (!_pp) return null;",
292                 "auto _p = WlProxy.get(_pp);",
293                 "if (_p) return _p;",
294                 "return iface.makeProxy(_pp);"  ]
295         );
296     }
297 
298     void writePrivListenerSig(SourceFile sf)
299     {
300         enum fstLine = "void function(";
301         immutable lstEol = format(") %s;", cEvName);
302 
303         immutable indent = ' '.repeat.take(fstLine.length).array();
304         sf.writeln("%svoid* data,", fstLine);
305         auto eol = args.empty ? lstEol : ",";
306         sf.writeln("%swl_proxy* proxy%s", indent, eol);
307         foreach(i, arg; enumerate(clArgs))
308         {
309             eol = i == args.length-1 ? lstEol : ",";
310             sf.writeln("%s%s %s%s", indent, arg.evCType, arg.paramName, eol);
311         }
312     }
313 
314     void writePrivListenerStub(SourceFile sf)
315     {
316         immutable fstLine = format("void wl_d_on_%s_%s(", ifaceName, name);
317         immutable indent = ' '.repeat.take(fstLine.length).array();
318         sf.writeln("%svoid* data,", fstLine);
319         auto eol = args.empty ? ")" : ",";
320         sf.writeln("%swl_proxy* proxy%s", indent, eol);
321         foreach(i, arg; enumerate(clArgs))
322         {
323             eol = i == args.length-1 ? ")" : ",";
324             sf.writeln("%s%s %s%s", indent, arg.evCType, arg.paramName, eol);
325         }
326         sf.bracedBlock!({
327             sf.writeln("nothrowFnWrapper!({");
328             sf.indentedBlock!({
329                 sf.writeln("auto _p = WlProxy.get(proxy);");
330                 sf.writeln("assert(_p, \"listener stub without proxy\");");
331                 sf.writeln("auto _i = cast(%s)_p;", ifaceDName(ifaceName));
332                 sf.writeln("assert(_i, \"listener stub proxy is not %s\");", ifaceDName(ifaceName));
333                 sf.writeln("if (_i._%s)", dHandlerName);
334                 sf.bracedBlock!({
335                     string sep = args.length ? ", " : "";
336                     sf.write("_i._%s(_i%s", dHandlerName, sep);
337                     foreach (i, arg; args)
338                     {
339                         sep = (i == args.length-1) ? "" : ", ";
340                         sf.write("%s%s", arg.dCastExpr(ifaceName), sep);
341                     }
342                     sf.writeln(");");
343                 });
344 
345             });
346             sf.writeln("});");
347         });
348     }
349 }
350 
351 
352 class ClientInterface : Interface
353 {
354     this (Element el, Protocol protocol)
355     {
356         super(el, protocol);
357     }
358 
359     @property auto clRequests()
360     {
361         return requests.map!(r => cast(ClientMessage)r);
362     }
363 
364     @property auto clEvents()
365     {
366         return events.map!(r => cast(ClientMessage)r);
367     }
368 
369     @property string globalNativeListenerName()
370     {
371         return format("wl_d_%s_listener", name);
372     }
373 
374     override void writeCode(SourceFile sf)
375     {
376         description.writeCode(sf);
377 
378         sf.writeln("final class %s : %s", dName,
379             name == "wl_display" ?
380                 "WlDisplayBase" :
381                 "WlProxy");
382         sf.bracedBlock!(
383         {
384             writeVersion(sf);
385             sf.writeln();
386             sf.writeln("/// Build a %s from a native object.", dName);
387             sf.writeln(name == "wl_display" ?
388                 "package(wayland) this(wl_display* native)" :
389                 "private this(wl_proxy* native)"
390             );
391             sf.bracedBlock!({
392                 sf.writeln("super(native);");
393                 if (writeEvents)
394                 {
395                     sf.writeln(
396                         "wl_proxy_add_listener(proxy, cast(void_func_t*)&%s, null);",
397                         globalNativeListenerName
398                     );
399                 }
400             });
401             sf.writeln();
402             sf.writeln("/// Interface object that creates %s objects.", dName);
403             sf.writeln("static @property immutable(WlProxyInterface) iface()");
404             sf.bracedBlock!({
405                 sf.writeln("return %sIface;", camelName(name));
406             });
407             writeConstants(sf);
408             if (writeEvents)
409             {
410                 sf.writeln();
411                 foreach(msg; clEvents)
412                 {
413                     msg.writeEventDgAlias(sf);
414                 }
415             }
416             foreach (en; enums)
417             {
418                 sf.writeln();
419                 en.writeCode(sf);
420             }
421             writeDtorCode(sf);
422             foreach (msg; clRequests)
423             {
424                 sf.writeln();
425                 msg.writeRequestCode(sf);
426             }
427             if (writeEvents)
428             {
429                 foreach(msg; clEvents)
430                 {
431                     sf.writeln();
432                     msg.writeEventDgAccessor(sf);
433                 }
434 
435                 sf.writeln();
436                 foreach(msg; events)
437                 {
438                     sf.writeln("private %s _%s;", msg.dEvDgType, msg.dHandlerName);
439                 }
440             }
441         });
442     }
443 
444     void writeConstants(SourceFile sf)
445     {
446         if (requests.length)
447         {
448             sf.writeln();
449             foreach(i, msg; requests)
450             {
451                 sf.writeln("/// Op-code of %s.%s.", dName, msg.dMethodName);
452                 sf.writeln("enum %s = %d;", msg.opCodeName, i);
453             }
454             sf.writeln();
455             foreach(msg; requests)
456             {
457                 sf.writeln(
458                     "/// Version of %s protocol introducing %s.%s.",
459                     protocol.name, dName, msg.dMethodName
460                 );
461                 sf.writeln("enum %sSinceVersion = %d;", camelName(msg.name), msg.since);
462             }
463         }
464         if (events.length)
465         {
466             sf.writeln();
467             foreach(msg; events)
468             {
469                 sf.writeln(
470                     "/// %s protocol version introducing %s.%s.",
471                     protocol.name, dName, msg.dHandlerName
472                 );
473                 sf.writeln("enum %sSinceVersion = %d;", msg.dHandlerName, msg.since);
474             }
475         }
476     }
477 
478     void writeDtorCode(SourceFile sf)
479     {
480         immutable hasDtor = requests.canFind!(rq => rq.isDtor);
481         immutable hasDestroy = requests.canFind!(rq => rq.name == "destroy");
482 
483         enforce(!hasDestroy || hasDtor);
484 
485         if (!hasDestroy && name != "wl_display")
486         {
487             sf.writeln();
488             sf.writeln("/// Destroy this %s object.", dName);
489             sf.writeln("void destroy()");
490             sf.bracedBlock!({
491                 sf.writeln("wl_proxy_destroy(proxy);");
492                 sf.writeln("super.destroyNotify();");
493             });
494         }
495     }
496 
497     void writePrivListener(SourceFile sf)
498     {
499         if (!writeEvents) return;
500 
501         sf.writeln("struct %s_listener", name);
502         sf.bracedBlock!({
503             foreach(ev; clEvents)
504             {
505                 ev.writePrivListenerSig(sf);
506             }
507         });
508 
509         sf.writeln();
510         immutable fstLine = format("__gshared %s = %s_listener (", globalNativeListenerName, name);
511         immutable indent = ' '.repeat(fstLine.length).array();
512         foreach (i, ev; events)
513         {
514             sf.writeln("%s&wl_d_on_%s_%s%s", (i == 0 ? fstLine : indent),
515                                         name, ev.name,
516                                         (i == events.length-1) ? ");" : ",");
517         }
518     }
519 
520     void writePrivListenerStubs(SourceFile sf)
521     {
522         if (!writeEvents) return;
523 
524         foreach(ev; clEvents)
525         {
526             sf.writeln();
527             ev.writePrivListenerStub(sf);
528         }
529     }
530 }
531 
532 
533 
534 class ClientProtocol : Protocol
535 {
536     this(Element el)
537     {
538         super(el);
539     }
540 
541     @property auto clIfaces()
542     {
543         return ifaces.map!(iface => cast(ClientInterface)iface);
544     }
545 
546     override void writeCode(SourceFile sf, in Options opt)
547     {
548         writeHeader(sf, opt);
549         if (name == "wayland") sf.writeln("import wayland.client.core;");
550         else sf.writeln("import wayland.client;");
551         sf.writeln("import wayland.native.client;");
552         sf.writeln("import wayland.native.util;");
553         sf.writeln("import wayland.util;");
554         sf.writeln();
555         sf.writeln("import std.exception : enforce;");
556         sf.writeln("import std.string : fromStringz, toStringz;");
557         sf.writeln();
558 
559         foreach(iface; ifaces)
560         {
561             iface.writeCode(sf);
562             sf.writeln();
563         }
564 
565         // writing private code
566         sf.writeln("private:");
567         sf.writeln();
568 
569         writePrivIfaces(sf);
570         sf.writeln();
571 
572         sf.writeln("extern(C) nothrow");
573         sf.bracedBlock!({
574             foreach(i, iface; enumerate(clIfaces))
575             {
576                 if (i != 0) sf.writeln();
577                 iface.writePrivListener(sf);
578                 iface.writePrivListenerStubs(sf);
579             }
580         });
581     }
582 
583     override void writePrivIfaces(SourceFile sf)
584     {
585         foreach(iface; ifaces)
586         {
587             sf.writeln("immutable WlProxyInterface %sIface;", camelName(iface.name));
588         }
589         foreach (iface; ifaces)
590         {
591             sf.writeln();
592             sf.writeln("immutable final class %sIface : WlProxyInterface",
593                     titleCamelName(iface.name));
594             sf.bracedBlock!({
595                 sf.writeln("this(immutable wl_interface* native)");
596                 sf.bracedBlock!({
597                     sf.writeln("super(native);");
598                 });
599                 sf.writeln("override WlProxy makeProxy(wl_proxy* proxy) immutable");
600                 sf.bracedBlock!({
601                     if (iface.name == "wl_display")
602                     {
603                         sf.writeln("return new WlDisplay(cast(wl_display*)proxy);");
604                     }
605                     else
606                         sf.writeln("return new %s(proxy);", iface.dName);
607                 });
608             });
609         }
610         writeNativeIfaces(sf);
611     }
612 
613     override void writeNativeIfacesAssignment(SourceFile sf)
614     {
615         foreach (iface; ifaces)
616         {
617             sf.writeln("%sIface = new immutable %sIface( &wl_ifaces[%s] );",
618                     camelName(iface.name),
619                     titleCamelName(iface.name),
620                     indexSymbol(iface.name));
621         }
622     }
623 
624 }