#include "AFBClient.h" #include #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); } constexpr static struct afb_wsj1_itf itf = { onHangup, onCall, onEvent, }; // XXX: I am not sure this is the right thing to do though... 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; std::atomic returned; returned.store(false, std::memory_order_relaxed); 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.store(true, std::memory_order_release); }; // 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.load(std::memory_order_consume)) { dispatch_internal(loop); } // return the actual API call result rc = call_rc; } return rc; } } // namespace // _ ___ _ // ___| | __ _ ___ ___ |_ _|_ __ ___ _ __ | | // / __| |/ _` / __/ __| | || '_ ` _ \| '_ \| | // | (__| | (_| \__ \__ \ | || | | | | | |_) | | // \___|_|\__,_|___/___/ |___|_| |_| |_| .__/|_| // |_| class AFBClient::Impl { friend class AFBClient; // This is the AFBClient interface impl int init(int port, char const *token); int dispatch(); // WM API int requestSurface(const char *label); int activateSurface(const char *label); int deactivateSurface(const char *label); int endDraw(const char *label); void set_event_handler(enum EventType et, std::function f); Impl(); ~Impl(); struct afb_wsj1 *wsj1; struct sd_event *loop; }; AFBClient::Impl::Impl() : wsj1{}, loop{} { TRACE(); } AFBClient::Impl::~Impl() { TRACE(); afb_wsj1_unref(wsj1); sd_event_unref(loop); loop = nullptr; } int AFBClient::Impl::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, const_cast(&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::Impl::dispatch() { std::lock_guard guard(dispatch_mutex); return sd_event_run(loop, 1); } int AFBClient::Impl::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::Impl::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::Impl::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::Impl::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::Impl::set_event_handler( enum EventType et, std::function func) { UNUSED(et); UNUSED(func); TRACE(); // XXX todo } // _ _ _____ ____ ____ _ _ _ // ___| | __ _ ___ ___ / \ | ___| __ ) / ___| (_) ___ _ __ | |_ // / __| |/ _` / __/ __| / _ \ | |_ | _ \| | | | |/ _ \ '_ \| __| // | (__| | (_| \__ \__ \ / ___ \| _| | |_) | |___| | | __/ | | | |_ // \___|_|\__,_|___/___/ /_/ \_\_| |____/ \____|_|_|\___|_| |_|\__| // int AFBClient::init(int port, char const *token) { return this->d->init(port, token); } int AFBClient::dispatch() { return this->d->dispatch(); } int AFBClient::requestSurface(const char *label) { return this->d->requestSurface(label); } int AFBClient::activateSurface(const char *label) { return this->d->activateSurface(label); } int AFBClient::deactivateSurface(const char *label) { return this->d->deactivateSurface(label); } int AFBClient::endDraw(const char *label) { return this->d->endDraw(label); } void AFBClient::set_event_handler(enum EventType et, std::function f) { return this->d->set_event_handler(et, std::move(f)); } AFBClient &AFBClient::instance() { TRACE(); static AFBClient obj; return obj; } AFBClient::AFBClient() : d(new Impl) {} AFBClient::~AFBClient() { delete d; }