summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarius Vlad <marius.vlad@collabora.com>2022-10-09 12:52:14 +0300
committerMarius Vlad <marius.vlad@collabora.com>2022-10-21 20:16:53 +0300
commit68dab999a68e42af926e8e6b1f7adb834bea6d51 (patch)
tree025f5ee22f9481d40aa7140b304abad092e35fac
parent8b661b747c67e7af7730d058392980a08ca3f07a (diff)
clients/grpc: Initial start for gRPC proxy
The proxy helper client would bind to the agl-shell protocol interface but also create a gRPC server which implements the agl-shell protobuf interface. The gRPC server implementation created in this helper client would be used by regular clients if they would require further change the window management or change window properties. Note that this is an initial bring-up with some of implementation being a stub, as the window properties would actually be implemented when expanding the agl-shell private extension. The hooks in the gRPC implementation that are in place are: - xxx - yyy - zzz - ttt Signed-off-by: Marius Vlad <marius.vlad@collabora.com> Change-Id: I5f5e8426d52ed7bf2e264be78c6572388afa53af
-rw-r--r--clients/grpc.cpp492
-rw-r--r--clients/grpc.h47
-rw-r--r--clients/meson.build61
-rw-r--r--meson.build2
-rw-r--r--protocol/agl_shell.proto29
5 files changed, 624 insertions, 7 deletions
diff --git a/clients/grpc.cpp b/clients/grpc.cpp
new file mode 100644
index 0000000..503453d
--- /dev/null
+++ b/clients/grpc.cpp
@@ -0,0 +1,492 @@
+#include <cstdio>
+#include <ctime>
+#include <algorithm>
+
+#include "grpc.h"
+
+struct shell_data {
+ struct wl_display *wl_display;
+ struct agl_shell *shell;
+ struct agl_shell_ext *shell_ext;
+ Shell *aglShell;
+
+ bool wait_for_bound;
+ bool wait_for_doas;
+
+ bool bound_ok;
+ bool doas_ok;
+
+ uint32_t version;
+ struct wl_list output_list; /** window_output::link */
+};
+
+struct window_output {
+ struct shell_data *shell_data;
+ struct wl_output *output;
+ char *name;
+ struct wl_list link; /** display::output_list */
+};
+
+static struct shell_data *sh = nullptr;
+
+grpc::ServerUnaryReactor *
+GrpcServiceImpl::ActivateApp(grpc::CallbackServerContext *context,
+ const ::agl_shell_ipc::ActivateRequest* request,
+ google::protobuf::Empty* /*response*/)
+{
+ fprintf(stderr, "activating app %s on output %s\n",
+ request->app_id().c_str(),
+ request->output_name().c_str());
+
+ sh->aglShell->ActivateApp(request->app_id(), request->output_name());
+
+ grpc::ServerUnaryReactor* reactor = context->DefaultReactor();
+ reactor->Finish(grpc::Status::OK);
+ return reactor;
+}
+
+grpc::ServerUnaryReactor *
+GrpcServiceImpl::DeactivateApp(grpc::CallbackServerContext *context,
+ const ::agl_shell_ipc::DeactivateRequest* request,
+ google::protobuf::Empty* /*response*/)
+{
+ sh->aglShell->DeactivateApp(request->app_id());
+
+ grpc::ServerUnaryReactor* reactor = context->DefaultReactor();
+ reactor->Finish(grpc::Status::OK);
+ return reactor;
+}
+
+grpc::ServerUnaryReactor *
+GrpcServiceImpl::SetAppFloat(grpc::CallbackServerContext *context,
+ const ::agl_shell_ipc::FloatRequest* request,
+ google::protobuf::Empty* /* response */)
+{
+ sh->aglShell->SetAppFloat(request->app_id());
+
+ grpc::ServerUnaryReactor* reactor = context->DefaultReactor();
+ reactor->Finish(grpc::Status::OK);
+ return reactor;
+}
+
+grpc::ServerUnaryReactor *
+GrpcServiceImpl::SetAppSplit(grpc::CallbackServerContext *context,
+ const ::agl_shell_ipc::SplitRequest* request,
+ google::protobuf::Empty* /*response*/)
+{
+ sh->aglShell->SetAppSplit(request->app_id(), request->tile_orientation());
+
+ grpc::ServerUnaryReactor* reactor = context->DefaultReactor();
+ reactor->Finish(grpc::Status::OK);
+ return reactor;
+}
+
+void
+Shell::ActivateApp(const std::string &app_id, const std::string &output_name)
+{
+ struct window_output *woutput, *w_output;
+
+ woutput = nullptr;
+ w_output = nullptr;
+
+ wl_list_for_each(woutput, &sh->output_list, link) {
+ if (woutput->name && !strcmp(woutput->name, output_name.c_str())) {
+ w_output = woutput;
+ break;
+ }
+ }
+
+ // else, get the first one available
+ if (!w_output)
+ w_output = wl_container_of(sh->output_list.prev, w_output, link);
+
+ agl_shell_activate_app(this->m_shell.get(), app_id.c_str(), w_output->output);
+ wl_display_flush(sh->wl_display);
+}
+
+void
+Shell::DeactivateApp(const std::string &app_id)
+{
+ (void) app_id;
+}
+
+void
+Shell::SetAppFloat(const std::string &app_id)
+{
+ (void) app_id;
+}
+
+void
+Shell::SetAppSplit(const std::string &app_id, uint32_t orientation)
+{
+ (void) app_id;
+ (void) orientation;
+}
+
+static void
+start_grpc_server(void)
+{
+ // instantiante the grpc server
+ std::string server_address(kDefaultGrpcServiceAddress);
+ GrpcServiceImpl service;
+
+ grpc::EnableDefaultHealthCheckService(true);
+ grpc::reflection::InitProtoReflectionServerBuilderPlugin();
+
+ grpc::ServerBuilder builder;
+ builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
+ builder.RegisterService(&service);
+
+ std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
+ fprintf(stderr, "Server listening on %s\n", server_address.c_str());
+
+ server->Wait();
+}
+
+static void
+agl_shell_bound_ok(void *data, struct agl_shell *agl_shell)
+{
+ (void) agl_shell;
+
+ struct shell_data *sh = static_cast<struct shell_data *>(data);
+ sh->wait_for_bound = false;
+
+ sh->bound_ok = true;
+}
+
+static void
+agl_shell_bound_fail(void *data, struct agl_shell *agl_shell)
+{
+ (void) agl_shell;
+
+ struct shell_data *sh = static_cast<struct shell_data *>(data);
+ sh->wait_for_bound = false;
+
+ sh->bound_ok = false;
+}
+
+static void
+agl_shell_app_state(void *data, struct agl_shell *agl_shell,
+ const char *app_id, uint32_t state)
+{
+ (void) data;
+ (void) agl_shell;
+ (void) app_id;
+ (void) state;
+}
+
+static const struct agl_shell_listener shell_listener = {
+ agl_shell_bound_ok,
+ agl_shell_bound_fail,
+ agl_shell_app_state,
+};
+
+static void
+agl_shell_ext_doas_done(void *data, struct agl_shell_ext *agl_shell_ext, uint32_t status)
+{
+ (void) agl_shell_ext;
+
+ struct shell_data *sh = static_cast<struct shell_data *>(data);
+ sh->wait_for_doas = false;
+
+ if (status == AGL_SHELL_EXT_DOAS_SHELL_CLIENT_STATUS_SUCCESS)
+ sh->doas_ok = true;
+}
+
+static const struct agl_shell_ext_listener shell_ext_listener = {
+ agl_shell_ext_doas_done,
+};
+
+static void
+display_handle_geometry(void *data, struct wl_output *wl_output,
+ int x, int y, int physical_width, int physical_height,
+ int subpixel, const char *make, const char *model, int transform)
+{
+ (void) data;
+ (void) wl_output;
+ (void) x;
+ (void) y;
+ (void) physical_width;
+ (void) physical_height;
+ (void) subpixel;
+ (void) make;
+ (void) model;
+ (void) transform;
+}
+
+static void
+display_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags,
+ int width, int height, int refresh)
+{
+ (void) data;
+ (void) wl_output;
+ (void) flags;
+ (void) width;
+ (void) height;
+ (void) refresh;
+}
+
+static void
+display_handle_done(void *data, struct wl_output *wl_output)
+{
+ (void) data;
+ (void) wl_output;
+}
+
+static void
+display_handle_scale(void *data, struct wl_output *wl_output, int32_t factor)
+{
+ (void) data;
+ (void) wl_output;
+ (void) factor;
+}
+
+
+static void
+display_handle_name(void *data, struct wl_output *wl_output, const char *name)
+{
+ (void) wl_output;
+
+ struct window_output *woutput = static_cast<struct window_output *>(data);
+ woutput->name = strdup(name);
+}
+
+static void
+display_handle_description(void *data, struct wl_output *wl_output, const char *description)
+{
+ (void) data;
+ (void) wl_output;
+ (void) description;
+}
+
+static const struct wl_output_listener output_listener = {
+ display_handle_geometry,
+ display_handle_mode,
+ display_handle_done,
+ display_handle_scale,
+ display_handle_name,
+ display_handle_description,
+};
+
+static void
+display_add_output(struct shell_data *sh, struct wl_registry *reg,
+ uint32_t id, uint32_t version)
+{
+ struct window_output *w_output;
+
+ w_output = new struct window_output;
+ w_output->shell_data = sh;
+
+ w_output->output =
+ static_cast<struct wl_output *>(wl_registry_bind(reg, id,
+ &wl_output_interface,
+ std::min(version, static_cast<uint32_t>(4))));
+
+ wl_list_insert(&sh->output_list, &w_output->link);
+ wl_output_add_listener(w_output->output, &output_listener, w_output);
+}
+
+static void
+destroy_output(struct window_output *w_output)
+{
+ free(w_output->name);
+ wl_list_remove(&w_output->link);
+ free(w_output);
+}
+
+
+static void
+global_add(void *data, struct wl_registry *reg, uint32_t id,
+ const char *interface, uint32_t version)
+{
+
+ struct shell_data *sh = static_cast<struct shell_data *>(data);
+
+ if (!sh)
+ return;
+
+ if (strcmp(interface, agl_shell_interface.name) == 0) {
+ sh->shell =
+ static_cast<struct agl_shell *>(wl_registry_bind(reg, id,
+ &agl_shell_interface, std::min(static_cast<uint32_t>(3),
+ version)));
+ agl_shell_add_listener(sh->shell, &shell_listener, data);
+ sh->version = version;
+ } else if (strcmp(interface, "wl_output") == 0) {
+ display_add_output(sh, reg, id, version);
+ }
+}
+
+static void
+global_remove(void *data, struct wl_registry *reg, uint32_t id)
+{
+ /* Don't care */
+ (void) data;
+ (void) reg;
+ (void) id;
+}
+
+static void
+global_add_ext(void *data, struct wl_registry *reg, uint32_t id,
+ const char *interface, uint32_t version)
+{
+ struct shell_data *sh = static_cast<struct shell_data *>(data);
+
+ if (!sh)
+ return;
+
+ if (strcmp(interface, agl_shell_ext_interface.name) == 0) {
+ sh->shell_ext =
+ static_cast<struct agl_shell_ext *>(wl_registry_bind(reg, id,
+ &agl_shell_ext_interface, std::min(static_cast<uint32_t>(1),
+ version)));
+ agl_shell_ext_add_listener(sh->shell_ext, &shell_ext_listener, data);
+ }
+}
+
+static void
+global_remove_ext(void *data, struct wl_registry *reg, uint32_t id)
+{
+ /* Don't care */
+ (void) data;
+ (void) reg;
+ (void) id;
+}
+
+static const struct wl_registry_listener registry_ext_listener = {
+ global_add_ext,
+ global_remove_ext,
+};
+
+static const struct wl_registry_listener registry_listener = {
+ global_add,
+ global_remove,
+};
+
+static void
+register_shell_ext(struct wl_display *wl_display)
+{
+ struct wl_registry *registry;
+
+ registry = wl_display_get_registry(wl_display);
+
+ wl_registry_add_listener(registry, &registry_ext_listener, sh);
+
+ wl_display_roundtrip(wl_display);
+ wl_registry_destroy(registry);
+}
+
+static void
+register_shell(struct wl_display *wl_display)
+{
+ struct wl_registry *registry;
+
+ wl_list_init(&sh->output_list);
+
+ registry = wl_display_get_registry(wl_display);
+
+ wl_registry_add_listener(registry, &registry_listener, sh);
+
+ wl_display_roundtrip(wl_display);
+ wl_registry_destroy(registry);
+}
+
+static int
+start_agl_shell_client(void)
+{
+ int ret = 0;
+ struct wl_display *wl_display;
+
+ wl_display = wl_display_connect(NULL);
+
+ sh = new struct shell_data;
+ sh->wl_display = wl_display;
+ sh->wait_for_doas = true;
+ sh->wait_for_bound = true;
+
+ register_shell_ext(wl_display);
+
+ // check for agl_shell_ext
+ if (!sh->shell_ext) {
+ fprintf(stderr, "Failed to bind to agl_shell_ext interface\n");
+ return -1;
+ }
+
+ if (wl_list_empty(&sh->output_list)) {
+ fprintf(stderr, "Failed get any outputs!\n");
+ return -1;
+ }
+
+ agl_shell_ext_doas_shell_client(sh->shell_ext);
+ while (ret != -1 && sh->wait_for_doas) {
+ ret = wl_display_dispatch(sh->wl_display);
+ if (sh->wait_for_doas)
+ continue;
+ }
+
+ if (!sh->doas_ok) {
+ fprintf(stderr, "Failed to get doas_done event\n");
+ return -1;
+ }
+
+ // bind to agl-shell
+ register_shell(wl_display);
+ while (ret != -1 && sh->wait_for_bound) {
+ ret = wl_display_dispatch(sh->wl_display);
+ if (sh->wait_for_bound)
+ continue;
+ }
+
+ // at this point, we can't do anything about it
+ if (!sh->bound_ok) {
+ fprintf(stderr, "Failed to get bound_ok event!\n");
+ return -1;
+ }
+
+ fprintf(stderr, "agl_shell/agl_shell_ext interface OK\n");
+ std::shared_ptr<struct agl_shell> agl_shell{sh->shell, agl_shell_destroy};
+ sh->aglShell = new Shell(agl_shell);
+
+ return 0;
+}
+
+static void
+destroy_shell_data(void)
+{
+ struct window_output *w_output, *w_output_next;
+
+ wl_list_for_each_safe(w_output, w_output_next, &sh->output_list, link)
+ destroy_output(w_output);
+
+ wl_display_flush(sh->wl_display);
+ wl_display_disconnect(sh->wl_display);
+
+ delete sh;
+}
+
+int main(int argc, char **argv)
+{
+ (void) argc;
+ (void) argv;
+ int ret = 0;
+
+ // do not start right up, give shell client time to boot up
+ struct timespec ts = {};
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ ts.tv_sec = 2;
+ ts.tv_nsec = 0;
+
+ nanosleep(&ts, NULL);
+
+ ret = start_agl_shell_client();
+ if (ret) {
+ fprintf(stderr, "Failed to initialize agl-shell/agl-shell-ext\n");
+ exit(EXIT_FAILURE);
+ }
+
+ start_grpc_server();
+
+ destroy_shell_data();
+ return 0;
+}
diff --git a/clients/grpc.h b/clients/grpc.h
new file mode 100644
index 0000000..3a3c864
--- /dev/null
+++ b/clients/grpc.h
@@ -0,0 +1,47 @@
+#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"
+#include "agl-shell-client-protocol.h"
+
+namespace {
+ const char kDefaultGrpcServiceAddress[] = "127.0.0.1:14005";
+}
+
+
+class GrpcServiceImpl final : public agl_shell_ipc::AglShellManagerService::CallbackService {
+
+ grpc::ServerUnaryReactor *ActivateApp(grpc::CallbackServerContext *context,
+ const ::agl_shell_ipc::ActivateRequest* request,
+ google::protobuf::Empty* /*response*/);
+
+ grpc::ServerUnaryReactor *DeactivateApp(grpc::CallbackServerContext *context,
+ const ::agl_shell_ipc::DeactivateRequest* request,
+ google::protobuf::Empty* /*response*/);
+
+ grpc::ServerUnaryReactor *SetAppSplit(grpc::CallbackServerContext *context,
+ const ::agl_shell_ipc::SplitRequest* request,
+ google::protobuf::Empty* /*response*/);
+
+ grpc::ServerUnaryReactor *SetAppFloat(grpc::CallbackServerContext *context,
+ const ::agl_shell_ipc::FloatRequest* request,
+ google::protobuf::Empty* /*response*/);
+};
+
+
+class Shell {
+public:
+ std::shared_ptr<struct agl_shell> m_shell;
+ Shell(std::shared_ptr<struct agl_shell> shell) : m_shell(shell) { }
+ void ActivateApp(const std::string &app_id, const std::string &output_name);
+ void DeactivateApp(const std::string &app_id);
+ void SetAppSplit(const std::string &app_id, uint32_t orientation);
+ void SetAppFloat(const std::string &app_id);
+
+};
diff --git a/clients/meson.build b/clients/meson.build
index 08b2c08..ff27a60 100644
--- a/clients/meson.build
+++ b/clients/meson.build
@@ -1,5 +1,32 @@
dep_wayland_client = dependency('wayland-client', version: '>= 1.17.0')
+grpcpp_reflection_dep = cxx.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,
+]
+
clients = [
{
'basename': 'agl-screenshooter',
@@ -16,6 +43,18 @@ clients = [
'deps_objs' : [ dep_wayland_client ],
'deps': [ 'cairo' ],
},
+{
+ 'basename': 'agl-shell-grpc-server',
+ 'sources': [
+ 'grpc.cpp',
+ generated_protoc_sources,
+ generated_grpc_sources,
+ agl_shell_client_protocol_h,
+ agl_shell_protocol_c,
+ ],
+ 'deps_objs' : [ grpc_deps, dep_wayland_client ],
+ 'deps' : [],
+},
]
foreach t: clients
@@ -29,12 +68,22 @@ foreach t: clients
endif
endforeach
- executable(
- t_name, t.get('sources'),
- include_directories: [ common_inc ],
- dependencies: [ t_deps, libweston_dep ],
- install: true,
- )
+ if t_name == 'agl-shell-grpc-server'
+ executable(
+ t_name, t.get('sources'),
+ include_directories: [ common_inc ],
+ dependencies: [ t_deps, libweston_dep ],
+ install: true,
+ install_dir: dir_module_agl_compositor,
+ )
+ else
+ executable(
+ t_name, t.get('sources'),
+ include_directories: [ common_inc ],
+ dependencies: [ t_deps, libweston_dep ],
+ install: true,
+ )
+ endif
message('Building client ' + t_name)
endforeach
diff --git a/meson.build b/meson.build
index 0958f06..2ed517b 100644
--- a/meson.build
+++ b/meson.build
@@ -6,7 +6,7 @@ project('agl-compositor',
'c_std=gnu99',
'werror=true',
],
- meson_version: '>= 0.53',
+ meson_version: '>= 0.60',
license: 'MIT/Expat',
)
diff --git a/protocol/agl_shell.proto b/protocol/agl_shell.proto
new file mode 100644
index 0000000..721fac2
--- /dev/null
+++ b/protocol/agl_shell.proto
@@ -0,0 +1,29 @@
+syntax = "proto3";
+import "google/protobuf/empty.proto";
+package agl_shell_ipc;
+
+service AglShellManagerService {
+ rpc ActivateApp(ActivateRequest) returns (google.protobuf.Empty) {}
+ rpc DeactivateApp(DeactivateRequest) returns (google.protobuf.Empty) {}
+ rpc SetAppSplit(SplitRequest) returns (google.protobuf.Empty) {}
+ rpc SetAppFloat(FloatRequest) returns (google.protobuf.Empty) {}
+}
+
+message ActivateRequest {
+ string app_id = 1;
+ string output_name = 2;
+}
+
+message DeactivateRequest {
+ string app_id = 1;
+}
+
+message SplitRequest {
+ string app_id = 1;
+ int32 tile_orientation = 2;
+}
+
+message FloatRequest {
+ string app_id = 1;
+}
+