#include "AFBClient.h" #include #include #include #include #include #include #include #include #include #include extern "C" { #include #include } #define UNUSED(x) (void)(x) namespace { constexpr const int token_maxlen = 20; constexpr const char *const wmAPI = "winman"; #ifdef NDEBUG #define TRACE() #define TRACEN(N) #else #define CONCAT_(X, Y) X##Y #define CONCAT(X, Y) CONCAT_(X, Y) #define TRACE() \ ScopeTrace __attribute__((unused)) CONCAT(trace_scope_, __LINE__)(__func__) #define TRACEN(N) \ ScopeTrace __attribute__((unused)) CONCAT(named_trace_scope_, __LINE__)(#N) struct ScopeTrace { thread_local static int indent; char const *f{}; ScopeTrace(char const *func) : f(func) { fprintf(stderr, "%*s%s -->\n", 2 * indent++, "", this->f); } ~ScopeTrace() { fprintf(stderr, "%*s%s <--\n", 2 * --indent, "", this->f); } }; thread_local int ScopeTrace::indent = 0; #endif /* called when wsj1 receives a method invocation */ void onCall(void *closure, const char *api, const char *verb, struct afb_wsj1_msg *msg) { TRACE(); UNUSED(closure); int rc; printf("ON-CALL %s/%s:\n%s\n", api, verb, json_object_to_json_string_ext(afb_wsj1_msg_object_j(msg), JSON_C_TO_STRING_PRETTY)); fflush(stdout); rc = afb_wsj1_reply_error_s(msg, "\"unimplemented\"", nullptr); if (rc < 0) fprintf(stderr, "replying failed: %m\n"); } /* called when wsj1 receives an event */ void onEvent(void *closure, const char *event, afb_wsj1_msg *msg) { TRACE(); UNUSED(closure); printf("ON-EVENT %s:\n%s\n", event, json_object_to_json_string_ext(afb_wsj1_msg_object_j(msg), JSON_C_TO_STRING_PRETTY)); fflush(stdout); } /* called when wsj1 hangsup */ void onHangup(void *closure, afb_wsj1 *wsj1) { TRACE(); UNUSED(closure); UNUSED(wsj1); printf("ON-HANGUP\n"); fflush(stdout); exit(0); } static struct afb_wsj1_itf itf = { onHangup, onCall, onEvent, }; std::recursive_mutex dispatch_mutex; void dispatch_internal(struct sd_event *loop) { std::lock_guard guard(dispatch_mutex); TRACE(); sd_event_run(loop, -1); } /// object will be json_object_put int api_call(struct sd_event *loop, struct afb_wsj1 *wsj1, const char *verb, json_object *object, std::function onReply) { TRACE(); // We need to wrap the actual onReply call once in order to // *look* like a normal functions pointer (std::functions<> // with captures cannot convert to function pointers). // Alternatively we could setup a local struct and use it as // closure, but I think it is cleaner this way. int call_rc = 0; bool returned = false; std::function wrappedOnReply = [&returned, &call_rc, &onReply](bool ok, json_object *j) { TRACEN(wrappedOnReply); call_rc = ok ? 0 : -EINVAL; // We know it failed, but there may be an explanation in the // json object. { TRACEN(onReply); onReply(ok, j); } returned = true; }; // make the actual call, use wrappedOnReply as closure int rc = afb_wsj1_call_j( wsj1, wmAPI, verb, object, [](void *closure, afb_wsj1_msg *msg) { TRACEN(callClosure); auto *onReply = reinterpret_cast *>( closure); (*onReply)(!!afb_wsj1_msg_is_reply_ok(msg), afb_wsj1_msg_object_j(msg)); }, &wrappedOnReply); if (rc < 0) { fprintf( stderr, "calling %s/%s(%s) failed: %m\n", wmAPI, verb, json_object_to_json_string_ext(object, JSON_C_TO_STRING_PRETTY)); // Call the reply handler regardless with a NULL json_object* onReply(false, nullptr); } else { // We need to dispatch until "returned" got set, this is necessary // if events get triggered by the call (and would be dispatched before // the actual call-reply). while (!returned) { dispatch_internal(loop); } // return the actual API call result rc = call_rc; } return rc; } } // namespace AFBClient &AFBClient::instance() { TRACE(); static AFBClient obj; return obj; } AFBClient::AFBClient() : wsj1{}, loop{} { TRACE(); } AFBClient::~AFBClient() { TRACE(); afb_wsj1_unref(wsj1); sd_event_unref(loop); loop = nullptr; } int AFBClient::init(int port, char const *token) { TRACE(); char *uribuf = nullptr; int rc = -1; if (!token || strlen(token) > token_maxlen) { fprintf(stderr, "Token is invalid\n"); rc = -EINVAL; goto fail; } for (char const *p = token; *p; p++) { if (!isalnum(*p)) { fprintf(stderr, "Token is invalid\n"); rc = -EINVAL; goto fail; } } if (port < 1 && port > 0xffff) { fprintf(stderr, "Port is invalid\n"); rc = -EINVAL; goto fail; } /* get the default event loop */ rc = sd_event_default(&loop); if (rc < 0) { fprintf(stderr, "Connection to default event loop failed: %s\n", strerror(-rc)); goto fail; } asprintf(&uribuf, "ws://localhost:%d/api?token=%s", port, token); /* connect the websocket wsj1 to the uri given by the first argument */ wsj1 = afb_ws_client_connect_wsj1(loop, uribuf, &itf, nullptr); if (wsj1 == nullptr) { sd_event_unref(loop); fprintf(stderr, "Connection to %s failed: %m\n", uribuf); rc = -errno; goto fail; } return 0; fail: return rc; } int AFBClient::dispatch() { std::lock_guard guard(dispatch_mutex); return sd_event_run(loop, 1); } int AFBClient::requestSurface(const char *label) { TRACE(); json_object *jp = json_object_new_object(); json_object_object_add(jp, "drawing_name", json_object_new_string(label)); int rc = -1; /* send the request */ int rc2 = api_call( loop, wsj1, "request_surface", jp, [&rc](bool ok, json_object *j) { if (ok) { int id = json_object_get_int(json_object_object_get(j, "response")); char *buf; asprintf(&buf, "%d", id); printf("setenv(\"QT_IVI_SURFACE_ID\", %s, 1)\n", buf); if (setenv("QT_IVI_SURFACE_ID", buf, 1) != 0) { fprintf(stderr, "putenv failed: %m\n"); rc = -errno; } else { rc = 0; // Single point of success } } else { fprintf(stderr, "Could not get surface ID from WM: %s\n", j ? json_object_to_json_string_ext( j, JSON_C_TO_STRING_PRETTY) : "no-info"); rc = -EINVAL; } }); return rc2 < 0 ? rc2 : rc; } int AFBClient::activateSurface(const char *label) { TRACE(); json_object *j = json_object_new_object(); json_object_object_add(j, "drawing_name", json_object_new_string(label)); return api_call(loop, wsj1, "activate_surface", j, [](bool ok, json_object *j) { if (!ok) { fprintf( stderr, "API Call activate_surface() failed: %s\n", j ? json_object_to_json_string_ext(j, JSON_C_TO_STRING_PRETTY) : "no-info"); } }); } int AFBClient::deactivateSurface(const char *label) { TRACE(); json_object *j = json_object_new_object(); json_object_object_add(j, "drawing_name", json_object_new_string(label)); return api_call(loop, wsj1, "deactivate_surface", j, [](bool ok, json_object *j) { if (!ok) { fprintf( stderr, "API Call deactivate_surface() failed: %s\n", j ? json_object_to_json_string_ext(j, JSON_C_TO_STRING_PRETTY) : "no-info"); } }); } int AFBClient::endDraw(const char *label) { TRACE(); json_object *j = json_object_new_object(); json_object_object_add(j, "drawing_name", json_object_new_string(label)); return api_call(loop, wsj1, "enddraw", j, [](bool ok, json_object *j) { if (!ok) { fprintf( stderr, "API Call endDraw() failed: %s\n", j ? json_object_to_json_string_ext(j, JSON_C_TO_STRING_PRETTY) : "no-info"); } }); } void AFBClient::set_event_handler(enum EventType et, std::function func) { UNUSED(et); UNUSED(func); TRACE(); // XXX todo }