diff options
author | Marius Vlad <marius.vlad@collabora.com> | 2023-11-30 15:51:56 +0200 |
---|---|---|
committer | Marius Vlad <marius.vlad@collabora.com> | 2023-12-04 23:47:03 +0200 |
commit | f00c1e19f5c4cbcd185c8043f3062612bf1537f7 (patch) | |
tree | 453b6571c97cc90dbc98d8a8e3d654f7e9208ad2 | |
parent | 7fd8b36ce15d7617229a86a05e4e431631e684d5 (diff) |
tbtnavi: Add gRPC API support
This adds support for gRPC API specifically specifically to allow
placing the application on a different output, the remoting one.
Bug-AGL: SPEC-5003
Change-Id: Ibe7047b3b21dc67c44111a8b615a51588d2216d9
Signed-off-by: Marius Vlad <marius.vlad@collabora.com>
-rw-r--r-- | app/AglShellGrpcClient.cpp | 208 | ||||
-rw-r--r-- | app/AglShellGrpcClient.h | 114 | ||||
-rw-r--r-- | app/main.cpp | 9 | ||||
-rw-r--r-- | app/meson.build | 36 | ||||
-rw-r--r-- | app/protocol/agl_shell.proto | 110 |
5 files changed, 473 insertions, 4 deletions
diff --git a/app/AglShellGrpcClient.cpp b/app/AglShellGrpcClient.cpp new file mode 100644 index 0000000..8577f16 --- /dev/null +++ b/app/AglShellGrpcClient.cpp @@ -0,0 +1,208 @@ +//include stuff here +#include <cstdio> + +#include <mutex> +#include <condition_variable> +#include <chrono> +#include <grpc/grpc.h> +#include <grpcpp/grpcpp.h> +#include <grpcpp/server.h> +#include <grpcpp/server_builder.h> +#include <grpcpp/server_context.h> + +#include <grpcpp/ext/proto_server_reflection_plugin.h> +#include <grpcpp/health_check_service_interface.h> + +#include "AglShellGrpcClient.h" +#include "agl_shell.grpc.pb.h" + +using namespace std::chrono; + +namespace { + const char kDefaultGrpcServiceAddress[] = "127.0.0.1:14005"; +} + +GrpcClient::GrpcClient() +{ + m_channel = grpc::CreateChannel(kDefaultGrpcServiceAddress, + grpc::InsecureChannelCredentials()); + + // init the stub here + m_stub = agl_shell_ipc::AglShellManagerService::NewStub(m_channel); + reader = new Reader(m_stub.get()); +} + +void +GrpcClient::WaitForConnected(int wait_time_ms, int tries_timeout) +{ + struct timespec ts; + grpc_connectivity_state state; + int try_ = 0; + + clock_gettime(CLOCK_MONOTONIC, &ts); + ts.tv_sec = 0; + ts.tv_nsec = 500 * 1000 * 1000; // 500ms + + while (((state = m_channel->GetState(true)) != GRPC_CHANNEL_READY) && + try_++ < tries_timeout) { + fprintf(stderr, "waiting for channel state to be ready, current state %d", state); + nanosleep(&ts, NULL); + } + +} + + +bool +GrpcClient::ActivateApp(const std::string& app_id, const std::string& output_name) +{ + agl_shell_ipc::ActivateRequest request; + + request.set_app_id(app_id); + request.set_output_name(output_name); + + grpc::ClientContext context; + ::agl_shell_ipc::ActivateResponse reply; + + grpc::Status status = m_stub->ActivateApp(&context, request, &reply); + return status.ok(); +} + +bool +GrpcClient::DeactivateApp(const std::string& app_id) +{ + agl_shell_ipc::DeactivateRequest request; + + request.set_app_id(app_id); + + grpc::ClientContext context; + ::agl_shell_ipc::DeactivateResponse reply; + + grpc::Status status = m_stub->DeactivateApp(&context, request, &reply); + return status.ok(); +} + +bool +GrpcClient::SetAppFloat(const std::string& app_id, int32_t x_pos, int32_t y_pos) +{ + agl_shell_ipc::FloatRequest request; + + request.set_app_id(app_id); + + request.set_x_pos(x_pos); + request.set_y_pos(y_pos); + + grpc::ClientContext context; + ::agl_shell_ipc::FloatResponse reply; + + grpc::Status status = m_stub->SetAppFloat(&context, request, &reply); + return status.ok(); +} + +bool +GrpcClient::SetAppNormal(const std::string& app_id) +{ + agl_shell_ipc::NormalRequest request; + + request.set_app_id(app_id); + + grpc::ClientContext context; + ::agl_shell_ipc::NormalResponse reply; + + grpc::Status status = m_stub->SetAppNormal(&context, request, &reply); + return status.ok(); +} + +bool +GrpcClient::SetAppFullscreen(const std::string& app_id) +{ + agl_shell_ipc::FullscreenRequest request; + + request.set_app_id(app_id); + + grpc::ClientContext context; + ::agl_shell_ipc::FullscreenResponse reply; + + grpc::Status status = m_stub->SetAppFullscreen(&context, request, &reply); + return status.ok(); +} + +bool +GrpcClient::SetAppOnOutput(const std::string& app_id, const std::string &output) +{ + agl_shell_ipc::AppOnOutputRequest request; + + request.set_app_id(app_id); + request.set_output(output); + + grpc::ClientContext context; + ::agl_shell_ipc::AppOnOutputResponse reply; + + grpc::Status status = m_stub->SetAppOnOutput(&context, request, &reply); + return status.ok(); +} + +bool +GrpcClient::SetAppPosition(const std::string& app_id, int32_t x, int32_t y) +{ + agl_shell_ipc::AppPositionRequest request; + + request.set_app_id(app_id); + request.set_x(x); + request.set_y(y); + + grpc::ClientContext context; + ::agl_shell_ipc::AppPositionResponse reply; + + grpc::Status status = m_stub->SetAppPosition(&context, request, &reply); + return status.ok(); +} + +bool +GrpcClient::SetAppScale(const std::string& app_id, int32_t width, int32_t height) +{ + agl_shell_ipc::AppScaleRequest request; + + request.set_app_id(app_id); + request.set_width(width); + request.set_height(height); + + grpc::ClientContext context; + ::agl_shell_ipc::AppScaleResponse reply; + + grpc::Status status = m_stub->SetAppScale(&context, request, &reply); + return status.ok(); +} + + +grpc::Status +GrpcClient::Wait(void) +{ + return reader->Await(); +} + +void +GrpcClient::AppStatusState(Callback callback, void *data) +{ + reader->AppStatusState(callback, data); +} + +std::vector<std::string> +GrpcClient::GetOutputs() +{ + grpc::ClientContext context; + std::vector<std::string> v; + + ::agl_shell_ipc::OutputRequest request; + ::agl_shell_ipc::ListOutputResponse response; + + grpc::Status status = m_stub->GetOutputs(&context, request, &response); + if (!status.ok()) + return std::vector<std::string>(); + + for (int i = 0; i < response.outputs_size(); i++) { + ::agl_shell_ipc::OutputResponse rresponse = response.outputs(i); + v.push_back(rresponse.name()); + } + + return v; +} diff --git a/app/AglShellGrpcClient.h b/app/AglShellGrpcClient.h new file mode 100644 index 0000000..19cfb75 --- /dev/null +++ b/app/AglShellGrpcClient.h @@ -0,0 +1,114 @@ +#pragma once + +#include <cstdio> + +#include <mutex> +#include <condition_variable> +#include <grpc/grpc.h> +#include <grpcpp/grpcpp.h> +#include <grpcpp/server.h> +#include <grpcpp/server_builder.h> +#include <grpcpp/server_context.h> + +#include <grpcpp/ext/proto_server_reflection_plugin.h> +#include <grpcpp/health_check_service_interface.h> + +#include "agl_shell.grpc.pb.h" + +typedef void (*Callback)(agl_shell_ipc::AppStateResponse app_response, void *data); + +class Reader : public grpc::ClientReadReactor<::agl_shell_ipc::AppStateResponse> { +public: + Reader(agl_shell_ipc::AglShellManagerService::Stub *stub) + : m_stub(stub) + { + } + + void AppStatusState(Callback callback, void *_data) + { + ::agl_shell_ipc::AppStateRequest request; + + // set up the callback + m_callback = callback; + m_data = _data; + m_stub->async()->AppStatusState(&m_context, &request, this); + + StartRead(&m_app_state); + StartCall(); + } + + void OnReadDone(bool ok) override + { + if (ok) { + m_callback(m_app_state, m_data); + + // blocks in StartRead() if the server doesn't send + // antyhing + StartRead(&m_app_state); + } + } + + void SetDone() + { + fprintf(stderr, "%s()\n", __func__); + std::unique_lock<std::mutex> l(m_mutex); + m_done = true; + } + + void OnDone(const grpc::Status& s) override + { + fprintf(stderr, "%s()\n", __func__); + std::unique_lock<std::mutex> l(m_mutex); + + m_status = s; + + fprintf(stderr, "%s() done\n", __func__); + m_cv.notify_one(); + } + + grpc::Status Await() + { + std::unique_lock<std::mutex> l(m_mutex); + + m_cv.wait(l, [this] { return m_done; }); + + return std::move(m_status); + } +private: + grpc::ClientContext m_context; + ::agl_shell_ipc::AppStateResponse m_app_state; + void *m_data; + agl_shell_ipc::AglShellManagerService::Stub *m_stub; + + Callback m_callback; + + + std::mutex m_mutex; + std::condition_variable m_cv; + grpc::Status m_status; + bool m_done = false; +}; + +class GrpcClient { +public: + GrpcClient(); + void WaitForConnected(int wait_time_ms, int tries_timeout); + bool ActivateApp(const std::string& app_id, const std::string& output_name); + bool DeactivateApp(const std::string& app_id); + bool SetAppFloat(const std::string& app_id, int32_t x_pos, int32_t y_pos); + bool SetAppFullscreen(const std::string& app_id); + bool SetAppOnOutput(const std::string& app_id, const std::string& output); + bool SetAppNormal(const std::string& app_id); + bool SetAppPosition(const std::string& app_id, int32_t x, int32_t y); + bool SetAppScale(const std::string& app_id, int32_t width, int32_t height); + std::vector<std::string> GetOutputs(); + void GetAppState(); + void AppStatusState(Callback callback, void *data); + grpc::Status Wait(); + +private: + Reader *reader; + std::unique_ptr<agl_shell_ipc::AglShellManagerService::Stub> m_stub; + std::shared_ptr<grpc::Channel> m_channel; +}; + diff --git a/app/main.cpp b/app/main.cpp index 0b896b4..b3fd47a 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -14,10 +14,12 @@ #include "qcheapruler.hpp" #include "file_operation.h" +#include "AglShellGrpcClient.h" + int main(int argc, char *argv[]) { - QString graphic_role = QString("tbtnavi"); - struct agl_shell_desktop *agl_shell_desktop = nullptr; + std::string our_name = "tbtnavi"; + QString graphic_role = QString(our_name.c_str()); setenv("QT_QUICK_CONTROLS_STYLE", "AGL", 1); @@ -28,6 +30,9 @@ int main(int argc, char *argv[]) QCoreApplication::setApplicationVersion("0.1.0"); app.setDesktopFileName(graphic_role); + GrpcClient *client = new GrpcClient(); + client->SetAppOnOutput(our_name, "remoting-remote-1"); + // Load qml QQmlApplicationEngine engine; diff --git a/app/meson.build b/app/meson.build index 864edb7..a7d1e41 100644 --- a/app/meson.build +++ b/app/meson.build @@ -1,6 +1,33 @@ cpp = meson.get_compiler('cpp') qt5_dep = dependency('qt5', modules: ['Qml', 'Quick', 'Gui', 'Location']) +grpcpp_reflection_dep = cpp.find_library('grpc++_reflection') +protoc = find_program('protoc') +grpc_cpp = find_program('grpc_cpp_plugin') + +protoc_gen = generator(protoc, \ + output : ['@BASENAME@.pb.cc', '@BASENAME@.pb.h'], + arguments : ['--proto_path=@CURRENT_SOURCE_DIR@/protocol', + '--cpp_out=@BUILD_DIR@', + '@INPUT@']) + +generated_protoc_sources = protoc_gen.process('protocol/agl_shell.proto') + +grpc_gen = generator(protoc, \ + output : ['@BASENAME@.grpc.pb.cc', '@BASENAME@.grpc.pb.h'], + arguments : ['--proto_path=@CURRENT_SOURCE_DIR@/protocol', + '--grpc_out=@BUILD_DIR@', + '--plugin=protoc-gen-grpc=' + grpc_cpp.path(), + '@INPUT@']) +generated_grpc_sources = grpc_gen.process('protocol/agl_shell.proto') + +grpc_deps = [ + dependency('protobuf'), + dependency('grpc'), + dependency('grpc++'), + grpcpp_reflection_dep, +] + dep_qtappfw = [ dependency('qtappfw-navigation'), dependency('qtappfw-vehicle-signals'), @@ -9,12 +36,14 @@ dep_qtappfw = [ tbtnavi_dep = [ qt5_dep, dep_qtappfw, + grpc_deps ] tbtnavi_headers = [ 'qcheapruler.hpp', 'navigation_client.h', - 'file_operation.h' + 'file_operation.h', + 'AglShellGrpcClient.h' ] moc_files = qt5.compile_moc(headers: tbtnavi_headers, @@ -31,7 +60,10 @@ tbtnavi_src = [ 'main.cpp', 'navigation_client.cpp', 'qcheapruler.cpp', - 'file_operation.cpp' + 'file_operation.cpp', + 'AglShellGrpcClient.cpp', + generated_protoc_sources, + generated_grpc_sources, ] executable('tbtnavi', tbtnavi_src, resource_files, moc_files, diff --git a/app/protocol/agl_shell.proto b/app/protocol/agl_shell.proto new file mode 100644 index 0000000..c4f3dfe --- /dev/null +++ b/app/protocol/agl_shell.proto @@ -0,0 +1,110 @@ +syntax = "proto3"; +// using empty Response suitable better for forward compat +//import "google/protobuf/empty.proto"; +package agl_shell_ipc; + +service AglShellManagerService { + rpc ActivateApp(ActivateRequest) returns (ActivateResponse) {} + rpc DeactivateApp(DeactivateRequest) returns (DeactivateResponse) {} + rpc SetAppSplit(SplitRequest) returns (SplitResponse) {} + rpc SetAppFloat(FloatRequest) returns (FloatResponse) {} + rpc SetAppFullscreen(FullscreenRequest) returns (FullscreenResponse) {} + rpc AppStatusState(AppStateRequest) returns (stream AppStateResponse) {} + rpc GetOutputs(OutputRequest) returns (ListOutputResponse) {} + rpc SetAppNormal(NormalRequest) returns (NormalResponse) {} + rpc SetAppOnOutput(AppOnOutputRequest) returns (AppOnOutputResponse) {} + rpc SetAppPosition(AppPositionRequest) returns (AppPositionResponse) {} + rpc SetAppScale(AppScaleRequest) returns (AppScaleResponse) {} +} + +message ActivateRequest { + string app_id = 1; + string output_name = 2; +} + +message ActivateResponse { +}; + + +message DeactivateRequest { + string app_id = 1; +} + +message DeactivateResponse { +} + +message SplitRequest { + string app_id = 1; + int32 tile_orientation = 2; +} + +message SplitResponse { +} + +message FloatRequest { + string app_id = 1; + int32 x_pos = 2; + int32 y_pos = 3; +} + +message FloatResponse { +} + +message AppStateRequest { +} + +message AppStateResponse { + int32 state = 1; + string app_id = 2; +} + +message OutputRequest { +}; + +message OutputResponse { + string name = 1; +}; + +message ListOutputResponse { + repeated OutputResponse outputs = 1; +}; + +message NormalRequest { + string app_id = 1; +}; + +message NormalResponse { +}; + +message FullscreenRequest { + string app_id = 1; +}; + +message FullscreenResponse { +}; + +message AppOnOutputRequest { + string app_id = 1; + string output = 2; +}; + +message AppOnOutputResponse { +}; + +message AppPositionRequest { + string app_id = 1; + int32 x = 2; + int32 y = 3; +}; + +message AppPositionResponse { +}; + +message AppScaleRequest { + string app_id = 1; + int32 width = 2; + int32 height = 3; +}; + +message AppScaleResponse { +}; |