AFBClient: trace dispatch_internal(), header hygiene
[staging/windowmanager.git] / AFBClient.cpp
1 #include "AFBClient.h"
2
3 #include <cassert>
4 #include <cctype>
5 #include <cerrno>
6 #include <cstdio>
7 #include <cstdlib>
8 #include <cstring>
9
10 #include <unistd.h>
11
12 #include <systemd/sd-event.h>
13
14 #include <json-c/json.h>
15
16 extern "C" {
17 #include <afb/afb-ws-client.h>
18 #include <afb/afb-wsj1.h>
19 }
20
21 #define UNUSED(x) (void)(x)
22
23 namespace {
24
25 constexpr const int token_maxlen = 20;
26 constexpr const char *const wmAPI = "winman";
27
28 #ifdef NDEBUG
29 #define TRACE()
30 #define TRACEN(N)
31 #else
32 #define CONCAT_(X, Y) X##Y
33 #define CONCAT(X, Y) CONCAT_(X, Y)
34
35 #define TRACE() \
36     ScopeTrace __attribute__((unused)) CONCAT(trace_scope_, __LINE__)(__func__)
37 #define TRACEN(N) \
38     ScopeTrace __attribute__((unused)) CONCAT(named_trace_scope_, __LINE__)(#N)
39
40 struct ScopeTrace {
41     thread_local static int indent;
42     char const *f{};
43     ScopeTrace(char const *func) : f(func) {
44         fprintf(stderr, "%*s%s -->\n", 2 * indent++, "", this->f);
45     }
46     ~ScopeTrace() { fprintf(stderr, "%*s%s <--\n", 2 * --indent, "", this->f); }
47 };
48 thread_local int ScopeTrace::indent = 0;
49 #endif
50
51 /* called when wsj1 receives a method invocation */
52 void onCall(void *closure, const char *api, const char *verb,
53             struct afb_wsj1_msg *msg) {
54     TRACE();
55     UNUSED(closure);
56     int rc;
57     printf("ON-CALL %s/%s:\n%s\n", api, verb,
58            json_object_to_json_string_ext(afb_wsj1_msg_object_j(msg),
59                                           JSON_C_TO_STRING_PRETTY));
60     fflush(stdout);
61     rc = afb_wsj1_reply_error_s(msg, "\"unimplemented\"", nullptr);
62     if (rc < 0)
63         fprintf(stderr, "replying failed: %m\n");
64 }
65
66 /* called when wsj1 receives an event */
67 void onEvent(void *closure, const char *event, afb_wsj1_msg *msg) {
68     TRACE();
69     UNUSED(closure);
70     printf("ON-EVENT %s:\n%s\n", event,
71            json_object_to_json_string_ext(afb_wsj1_msg_object_j(msg),
72                                           JSON_C_TO_STRING_PRETTY));
73     fflush(stdout);
74 }
75
76 /* called when wsj1 hangsup */
77 void onHangup(void *closure, afb_wsj1 *wsj1) {
78     TRACE();
79     UNUSED(closure);
80     UNUSED(wsj1);
81     printf("ON-HANGUP\n");
82     fflush(stdout);
83     exit(0);
84 }
85
86 static struct afb_wsj1_itf itf = {
87     onHangup, onCall, onEvent,
88 };
89
90 void dispatch_internal(AFBClient *c, uint64_t timeout) {
91    TRACE();
92    c->dispatch(timeout);
93 }
94
95 }  // namespace
96
97 AFBClient &AFBClient::instance() {
98     TRACE();
99     static AFBClient obj;
100     return obj;
101 }
102
103 AFBClient::AFBClient() : wsj1{}, loop{} { TRACE(); }
104
105 AFBClient::~AFBClient() {
106     TRACE();
107     afb_wsj1_unref(wsj1);
108     sd_event_unref(loop);
109     loop = nullptr;
110 }
111
112 int AFBClient::init(int port, char const *token) {
113     TRACE();
114     char *uribuf = nullptr;
115     int rc = -1;
116
117     if (!token || strlen(token) > token_maxlen) {
118         fprintf(stderr, "Token is invalid\n");
119         rc = -EINVAL;
120         goto fail;
121     }
122
123     for (char const *p = token; *p; p++) {
124         if (!isalnum(*p)) {
125             fprintf(stderr, "Token is invalid\n");
126             rc = -EINVAL;
127             goto fail;
128         }
129     }
130
131     if (port < 1 && port > 0xffff) {
132         fprintf(stderr, "Port is invalid\n");
133         rc = -EINVAL;
134         goto fail;
135     }
136
137     /* get the default event loop */
138     rc = sd_event_default(&loop);
139     if (rc < 0) {
140         fprintf(stderr, "Connection to default event loop failed: %s\n",
141                 strerror(-rc));
142         goto fail;
143     }
144
145     asprintf(&uribuf, "ws://localhost:%d/api?token=%s", port, token);
146
147     /* connect the websocket wsj1 to the uri given by the first argument */
148     wsj1 = afb_ws_client_connect_wsj1(loop, uribuf, &itf, nullptr);
149     if (wsj1 == nullptr) {
150         sd_event_unref(loop);
151         fprintf(stderr, "Connection to %s failed: %m\n", uribuf);
152         rc = -errno;
153         goto fail;
154     }
155
156     return 0;
157
158 fail:
159     return rc;
160 }
161
162 int AFBClient::dispatch(uint64_t timeout) {
163     return sd_event_run(loop, timeout);
164 }
165
166 int AFBClient::requestSurface(const char *label) {
167     TRACE();
168     json_object *jp = json_object_new_object();
169     json_object_object_add(jp, "drawing_name", json_object_new_string(label));
170     int rc = -1;
171     /* send the request */
172     int rc2 = call("request_surface", jp, [&rc](bool ok, json_object *j) {
173         if (ok) {
174             int id = json_object_get_int(json_object_object_get(j, "response"));
175             char *buf;
176             asprintf(&buf, "%d", id);
177             printf("setenv(\"QT_IVI_SURFACE_ID\", %s, 1)\n", buf);
178             if (setenv("QT_IVI_SURFACE_ID", buf, 1) != 0) {
179                 fprintf(stderr, "putenv failed: %m\n");
180                 rc = -errno;
181             } else {
182                 rc = 0;  // Single point of success
183             }
184         } else {
185             fprintf(
186                 stderr, "Could not get surface ID from WM: %s\n",
187                 j ? json_object_to_json_string_ext(j, JSON_C_TO_STRING_PRETTY)
188                   : "no-info");
189             rc = -EINVAL;
190         }
191     });
192
193     return rc2 < 0 ? rc2 : rc;
194 }
195
196 int AFBClient::activateSurface(const char *label) {
197     TRACE();
198     json_object *j = json_object_new_object();
199     json_object_object_add(j, "drawing_name", json_object_new_string(label));
200     return call("activate_surface", j, [](bool ok, json_object *j) {
201         if (!ok) {
202             fprintf(
203                 stderr, "API Call activate_surface() failed: %s\n",
204                 j ? json_object_to_json_string_ext(j, JSON_C_TO_STRING_PRETTY)
205                   : "no-info");
206         }
207     });
208 }
209
210 int AFBClient::deactivateSurface(const char *label) {
211     TRACE();
212     json_object *j = json_object_new_object();
213     json_object_object_add(j, "drawing_name", json_object_new_string(label));
214     return call("deactivate_surface", j, [](bool ok, json_object *j) {
215         if (!ok) {
216             fprintf(
217                 stderr, "API Call deactivate_surface() failed: %s\n",
218                 j ? json_object_to_json_string_ext(j, JSON_C_TO_STRING_PRETTY)
219                   : "no-info");
220         }
221     });
222 }
223
224 int AFBClient::endDraw(const char *label) {
225     TRACE();
226     json_object *j = json_object_new_object();
227     json_object_object_add(j, "drawing_name", json_object_new_string(label));
228     return call("enddraw", j, [](bool ok, json_object *j) {
229         if (!ok) {
230             fprintf(
231                 stderr, "API Call endDraw() failed: %s\n",
232                 j ? json_object_to_json_string_ext(j, JSON_C_TO_STRING_PRETTY)
233                   : "no-info");
234         }
235     });
236 }
237
238 /// object will be json_object_put
239 int AFBClient::call(const char *verb, json_object *object,
240                     std::function<void(bool, json_object *)> onReply) {
241     TRACE();
242
243     // We need to wrap the actual onReply call once in order to
244     // *look* like a normal functions pointer (std::functions<>
245     // with captures cannot convert to function pointers).
246     // Alternatively we could setup a local struct and use it as
247     // closure, but I think it is cleaner this way.
248     int call_rc = 0;
249     bool returned = false;
250     std::function<void(bool, json_object *)> wrappedOnReply =
251         [&returned, &call_rc, &onReply](bool ok, json_object *j) {
252             TRACEN(wrappedOnReply);
253             call_rc = ok ? 0 : -EINVAL;
254             // We know it failed, but there may be an explanation in the
255             // json object.
256             onReply(ok, j);
257             returned = true;
258         };
259
260     // make the actual call, use wrappedOnReply as closure
261     int rc = afb_wsj1_call_j(
262         wsj1, wmAPI, verb, object,
263         [](void *closure, afb_wsj1_msg *msg) {
264             TRACEN(callClosure);
265             auto *onReply =
266                 reinterpret_cast<std::function<void(bool, json_object *)> *>(
267                     closure);
268             (*onReply)(!!afb_wsj1_msg_is_reply_ok(msg),
269                        afb_wsj1_msg_object_j(msg));
270         },
271         &wrappedOnReply);
272
273     if (rc < 0) {
274         fprintf(
275             stderr, "calling %s/%s(%s) failed: %m\n", wmAPI, verb,
276             json_object_to_json_string_ext(object, JSON_C_TO_STRING_PRETTY));
277         // Call the reply handler regardless with a NULL json_object*
278         onReply(false, nullptr);
279     } else {
280         // We need to dispatch until "returned" got set, this is necessary
281         // if events get triggered by the call (and would be dispatched before
282         // the actual call-reply).
283         while (!returned) {
284             dispatch_internal(this, -1);
285         }
286
287         // return the actual API call result
288         rc = call_rc;
289     }
290
291     return rc;
292 }
293
294 void AFBClient::set_event_handler(enum EventType et,
295                                   std::function<void(char const *)> func) {
296     UNUSED(et);
297     UNUSED(func);
298     TRACE();
299     // XXX todo
300 }