d3e3a5ec99146a501fb03fa5caf0f0346c86fcd3
[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 /// object will be json_object_put
96 int api_call(AFBClient *c, struct afb_wsj1 *wsj1, const char *verb,
97              json_object *object,
98              std::function<void(bool, json_object *)> onReply) {
99     TRACE();
100
101     // We need to wrap the actual onReply call once in order to
102     // *look* like a normal functions pointer (std::functions<>
103     // with captures cannot convert to function pointers).
104     // Alternatively we could setup a local struct and use it as
105     // closure, but I think it is cleaner this way.
106     int call_rc = 0;
107     bool returned = false;
108     std::function<void(bool, json_object *)> wrappedOnReply =
109         [&returned, &call_rc, &onReply](bool ok, json_object *j) {
110             TRACEN(wrappedOnReply);
111             call_rc = ok ? 0 : -EINVAL;
112             // We know it failed, but there may be an explanation in the
113             // json object.
114             {
115                 TRACEN(onReply);
116                 onReply(ok, j);
117             }
118             returned = true;
119         };
120
121     // make the actual call, use wrappedOnReply as closure
122     int rc = afb_wsj1_call_j(
123         wsj1, wmAPI, verb, object,
124         [](void *closure, afb_wsj1_msg *msg) {
125             TRACEN(callClosure);
126             auto *onReply =
127                 reinterpret_cast<std::function<void(bool, json_object *)> *>(
128                     closure);
129             (*onReply)(!!afb_wsj1_msg_is_reply_ok(msg),
130                        afb_wsj1_msg_object_j(msg));
131         },
132         &wrappedOnReply);
133
134     if (rc < 0) {
135         fprintf(
136             stderr, "calling %s/%s(%s) failed: %m\n", wmAPI, verb,
137             json_object_to_json_string_ext(object, JSON_C_TO_STRING_PRETTY));
138         // Call the reply handler regardless with a NULL json_object*
139         onReply(false, nullptr);
140     } else {
141         // We need to dispatch until "returned" got set, this is necessary
142         // if events get triggered by the call (and would be dispatched before
143         // the actual call-reply).
144         while (!returned) {
145             dispatch_internal(c, -1);
146         }
147
148         // return the actual API call result
149         rc = call_rc;
150     }
151
152     return rc;
153 }
154
155 }  // namespace
156
157 AFBClient &AFBClient::instance() {
158     TRACE();
159     static AFBClient obj;
160     return obj;
161 }
162
163 AFBClient::AFBClient() : wsj1{}, loop{} { TRACE(); }
164
165 AFBClient::~AFBClient() {
166     TRACE();
167     afb_wsj1_unref(wsj1);
168     sd_event_unref(loop);
169     loop = nullptr;
170 }
171
172 int AFBClient::init(int port, char const *token) {
173     TRACE();
174     char *uribuf = nullptr;
175     int rc = -1;
176
177     if (!token || strlen(token) > token_maxlen) {
178         fprintf(stderr, "Token is invalid\n");
179         rc = -EINVAL;
180         goto fail;
181     }
182
183     for (char const *p = token; *p; p++) {
184         if (!isalnum(*p)) {
185             fprintf(stderr, "Token is invalid\n");
186             rc = -EINVAL;
187             goto fail;
188         }
189     }
190
191     if (port < 1 && port > 0xffff) {
192         fprintf(stderr, "Port is invalid\n");
193         rc = -EINVAL;
194         goto fail;
195     }
196
197     /* get the default event loop */
198     rc = sd_event_default(&loop);
199     if (rc < 0) {
200         fprintf(stderr, "Connection to default event loop failed: %s\n",
201                 strerror(-rc));
202         goto fail;
203     }
204
205     asprintf(&uribuf, "ws://localhost:%d/api?token=%s", port, token);
206
207     /* connect the websocket wsj1 to the uri given by the first argument */
208     wsj1 = afb_ws_client_connect_wsj1(loop, uribuf, &itf, nullptr);
209     if (wsj1 == nullptr) {
210         sd_event_unref(loop);
211         fprintf(stderr, "Connection to %s failed: %m\n", uribuf);
212         rc = -errno;
213         goto fail;
214     }
215
216     return 0;
217
218 fail:
219     return rc;
220 }
221
222 int AFBClient::dispatch(uint64_t timeout) {
223     return sd_event_run(loop, timeout);
224 }
225
226 int AFBClient::requestSurface(const char *label) {
227     TRACE();
228     json_object *jp = json_object_new_object();
229     json_object_object_add(jp, "drawing_name", json_object_new_string(label));
230     int rc = -1;
231     /* send the request */
232     int rc2 = api_call(
233         this, wsj1, "request_surface", jp, [&rc](bool ok, json_object *j) {
234             if (ok) {
235                 int id =
236                     json_object_get_int(json_object_object_get(j, "response"));
237                 char *buf;
238                 asprintf(&buf, "%d", id);
239                 printf("setenv(\"QT_IVI_SURFACE_ID\", %s, 1)\n", buf);
240                 if (setenv("QT_IVI_SURFACE_ID", buf, 1) != 0) {
241                     fprintf(stderr, "putenv failed: %m\n");
242                     rc = -errno;
243                 } else {
244                     rc = 0;  // Single point of success
245                 }
246             } else {
247                 fprintf(stderr, "Could not get surface ID from WM: %s\n",
248                         j ? json_object_to_json_string_ext(
249                                 j, JSON_C_TO_STRING_PRETTY)
250                           : "no-info");
251                 rc = -EINVAL;
252             }
253         });
254
255     return rc2 < 0 ? rc2 : rc;
256 }
257
258 int AFBClient::activateSurface(const char *label) {
259     TRACE();
260     json_object *j = json_object_new_object();
261     json_object_object_add(j, "drawing_name", json_object_new_string(label));
262     return api_call(this, wsj1, "activate_surface", j, [](bool ok,
263                                                           json_object *j) {
264         if (!ok) {
265             fprintf(
266                 stderr, "API Call activate_surface() failed: %s\n",
267                 j ? json_object_to_json_string_ext(j, JSON_C_TO_STRING_PRETTY)
268                   : "no-info");
269         }
270     });
271 }
272
273 int AFBClient::deactivateSurface(const char *label) {
274     TRACE();
275     json_object *j = json_object_new_object();
276     json_object_object_add(j, "drawing_name", json_object_new_string(label));
277     return api_call(this, wsj1, "deactivate_surface", j, [](bool ok,
278                                                             json_object *j) {
279         if (!ok) {
280             fprintf(
281                 stderr, "API Call deactivate_surface() failed: %s\n",
282                 j ? json_object_to_json_string_ext(j, JSON_C_TO_STRING_PRETTY)
283                   : "no-info");
284         }
285     });
286 }
287
288 int AFBClient::endDraw(const char *label) {
289     TRACE();
290     json_object *j = json_object_new_object();
291     json_object_object_add(j, "drawing_name", json_object_new_string(label));
292     return api_call(this, wsj1, "enddraw", j, [](bool ok, json_object *j) {
293         if (!ok) {
294             fprintf(
295                 stderr, "API Call endDraw() failed: %s\n",
296                 j ? json_object_to_json_string_ext(j, JSON_C_TO_STRING_PRETTY)
297                   : "no-info");
298         }
299     });
300 }
301
302 void AFBClient::set_event_handler(enum EventType et,
303                                   std::function<void(char const *)> func) {
304     UNUSED(et);
305     UNUSED(func);
306     TRACE();
307     // XXX todo
308 }