summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--grpc-proxy/agl_shell.proto62
-rw-r--r--grpc-proxy/grpc-async-cb.cpp166
-rw-r--r--grpc-proxy/grpc-async-cb.h93
-rw-r--r--grpc-proxy/log.h11
-rw-r--r--grpc-proxy/main-grpc.cpp568
-rw-r--r--grpc-proxy/main-grpc.h64
-rw-r--r--grpc-proxy/meson.build46
-rw-r--r--grpc-proxy/shell.cpp78
-rw-r--r--grpc-proxy/shell.h45
-rw-r--r--meson.build4
-rw-r--r--meson_options.txt7
11 files changed, 1144 insertions, 0 deletions
diff --git a/grpc-proxy/agl_shell.proto b/grpc-proxy/agl_shell.proto
new file mode 100644
index 0000000..22fad1c
--- /dev/null
+++ b/grpc-proxy/agl_shell.proto
@@ -0,0 +1,62 @@
+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 AppStatusState(AppStateRequest) returns (stream AppStateResponse) {}
+ rpc GetOutputs(OutputRequest) returns (ListOutputResponse) {}
+}
+
+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;
+}
+
+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;
+};
diff --git a/grpc-proxy/grpc-async-cb.cpp b/grpc-proxy/grpc-async-cb.cpp
new file mode 100644
index 0000000..d0fd88d
--- /dev/null
+++ b/grpc-proxy/grpc-async-cb.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright © 2022 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <cstdio>
+#include <ctime>
+#include <algorithm>
+#include <queue>
+
+#define GRPC_CALLBACK_API_NONEXPERIMENTAL
+
+#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 "log.h"
+#include "agl_shell.grpc.pb.h"
+#include "grpc-async-cb.h"
+
+Lister::Lister(Shell *shell) : m_shell(shell)
+{
+ // don't call NextWrite() just yet we do it explicitly when getting
+ // the events from the compositor
+ m_writting = false;
+}
+
+void
+Lister::OnDone()
+{
+ delete this;
+}
+
+void Lister::OnWriteDone(bool ok)
+{
+ LOG("got ok %d\n", ok);
+ if (ok) {
+ LOG("done writting %d\n", m_writting);
+ m_writting = false;
+ }
+}
+
+void
+Lister::NextWrite(void)
+{
+ if (m_writting) {
+ LOG(">>>>> still in writting\n");
+ return;
+ }
+ m_writting = true;
+ StartWrite(&m_shell->m_shell_data->current_app_state);
+}
+
+bool
+Lister::Writting(void)
+{
+ return m_writting;
+}
+
+grpc::ServerUnaryReactor *
+GrpcServiceImpl::ActivateApp(grpc::CallbackServerContext *context,
+ const ::agl_shell_ipc::ActivateRequest* request,
+ ::agl_shell_ipc::ActivateResponse* /*response*/)
+{
+ LOG("activating app %s on output %s\n", request->app_id().c_str(),
+ request->output_name().c_str());
+
+ m_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,
+ ::agl_shell_ipc::DeactivateResponse* /*response*/)
+{
+ m_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,
+ ::agl_shell_ipc::FloatResponse* /* response */)
+{
+ m_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,
+ ::agl_shell_ipc::SplitResponse* /*response*/)
+{
+ m_aglShell->SetAppSplit(request->app_id(), request->tile_orientation());
+
+ grpc::ServerUnaryReactor* reactor = context->DefaultReactor();
+ reactor->Finish(grpc::Status::OK);
+ return reactor;
+}
+
+grpc::ServerUnaryReactor *
+GrpcServiceImpl::GetOutputs(grpc::CallbackServerContext *context,
+ const ::agl_shell_ipc::OutputRequest* /* request */,
+ ::agl_shell_ipc::ListOutputResponse* response)
+{
+ struct window_output *output;
+
+ struct wl_list *list = &m_aglShell->m_shell_data->output_list;
+ wl_list_for_each(output, list, link) {
+ auto m_output = response->add_outputs();
+ m_output->set_name(output->name);
+ }
+
+ grpc::ServerUnaryReactor* reactor = context->DefaultReactor();
+ reactor->Finish(grpc::Status::OK);
+ return reactor;
+}
+
+grpc::ServerWriteReactor<::agl_shell_ipc::AppStateResponse>*
+GrpcServiceImpl::AppStatusState(grpc::CallbackServerContext* context,
+ const ::agl_shell_ipc::AppStateRequest* /*request */)
+{
+
+ Lister *n = new Lister(m_aglShell);
+
+ m_aglShell->m_shell_data->server_context_list.push_back(std::pair(context, n));
+ LOG("added lister %p\n", static_cast<void *>(n));
+
+ // just return a Lister to keep the channel open
+ return n;
+}
diff --git a/grpc-proxy/grpc-async-cb.h b/grpc-proxy/grpc-async-cb.h
new file mode 100644
index 0000000..e6a19a6
--- /dev/null
+++ b/grpc-proxy/grpc-async-cb.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright © 2022 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#pragma once
+
+#include <memory>
+
+#define GRPC_CALLBACK_API_NONEXPERIMENTAL
+
+#include <grpc/grpc.h>
+#include <grpcpp/grpcpp.h>
+#include <grpcpp/server.h>
+#include <grpcpp/server_builder.h>
+#include <grpcpp/server_context.h>
+
+#include <mutex>
+#include <condition_variable>
+
+#include <grpcpp/ext/proto_server_reflection_plugin.h>
+#include <grpcpp/health_check_service_interface.h>
+
+#include "shell.h"
+#include "agl_shell.grpc.pb.h"
+
+namespace {
+ const char kDefaultGrpcServiceAddress[] = "127.0.0.1:14005";
+}
+
+class Lister : public grpc::ServerWriteReactor<::agl_shell_ipc::AppStateResponse> {
+public:
+ Lister(Shell *aglShell);
+ void OnDone() override;
+ void OnWriteDone(bool ok) override;
+ void NextWrite(void);
+ bool Writting(void);
+private:
+ Shell *m_shell;
+ bool m_writting;
+};
+
+class GrpcServiceImpl final : public agl_shell_ipc::AglShellManagerService::CallbackService {
+public:
+ GrpcServiceImpl(Shell *aglShell) : m_aglShell(aglShell) {}
+
+ grpc::ServerUnaryReactor *ActivateApp(grpc::CallbackServerContext *context,
+ const ::agl_shell_ipc::ActivateRequest* request,
+ ::agl_shell_ipc::ActivateResponse* /*response*/) override;
+
+ grpc::ServerUnaryReactor *DeactivateApp(grpc::CallbackServerContext *context,
+ const ::agl_shell_ipc::DeactivateRequest* request,
+ ::agl_shell_ipc::DeactivateResponse* /*response*/) override;
+
+ grpc::ServerUnaryReactor *SetAppFloat(grpc::CallbackServerContext *context,
+ const ::agl_shell_ipc::FloatRequest* request,
+ ::agl_shell_ipc::FloatResponse* /*response*/) override;
+
+ grpc::ServerUnaryReactor *SetAppSplit(grpc::CallbackServerContext *context,
+ const ::agl_shell_ipc::SplitRequest* request,
+ ::agl_shell_ipc::SplitResponse* /*response*/) override;
+
+ grpc::ServerUnaryReactor *GetOutputs(grpc::CallbackServerContext *context,
+ const ::agl_shell_ipc::OutputRequest* /* request */,
+ ::agl_shell_ipc::ListOutputResponse* response) override;
+
+ grpc::ServerWriteReactor< ::agl_shell_ipc::AppStateResponse>* AppStatusState(
+ ::grpc::CallbackServerContext* /*context*/,
+ const ::agl_shell_ipc::AppStateRequest* /*request*/) override;
+
+private:
+ Shell *m_aglShell;
+};
diff --git a/grpc-proxy/log.h b/grpc-proxy/log.h
new file mode 100644
index 0000000..d0a5275
--- /dev/null
+++ b/grpc-proxy/log.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <cstdio>
+
+//#define DEBUG
+
+#if !defined(LOG) && defined(DEBUG)
+#define LOG(fmt, ...) do { fprintf(stderr, "%s() " fmt, __func__, ##__VA_ARGS__); } while (0)
+#else
+#define LOG(fmt, ...) do {} while (0)
+#endif
diff --git a/grpc-proxy/main-grpc.cpp b/grpc-proxy/main-grpc.cpp
new file mode 100644
index 0000000..a59c282
--- /dev/null
+++ b/grpc-proxy/main-grpc.cpp
@@ -0,0 +1,568 @@
+/*
+ * Copyright © 2022 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <cstdio>
+#include <ctime>
+#include <algorithm>
+#include <queue>
+#include <thread>
+#include <mutex>
+#include <condition_variable>
+
+#include "shell.h"
+#include "log.h"
+#include "main-grpc.h"
+#include "grpc-async-cb.h"
+
+struct shell_data_init {
+ struct agl_shell *shell;
+ bool wait_for_bound;
+ bool bound_ok;
+ bool bound_fail;
+ int version;
+};
+
+static int running = 1;
+
+static void
+agl_shell_bound_ok_init(void *data, struct agl_shell *agl_shell)
+{
+ (void) agl_shell;
+
+ struct shell_data_init *sh = static_cast<struct shell_data_init *>(data);
+ sh->wait_for_bound = false;
+
+ sh->bound_ok = true;
+}
+
+static void
+agl_shell_bound_fail_init(void *data, struct agl_shell *agl_shell)
+{
+ (void) agl_shell;
+
+ struct shell_data_init *sh = static_cast<struct shell_data_init *>(data);
+ sh->wait_for_bound = false;
+
+ sh->bound_fail = true;
+}
+
+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) agl_shell;
+ struct shell_data *sh = static_cast<struct shell_data *>(data);
+ LOG("got app_state event app_id %s, state %d\n", app_id, state);
+
+ if (sh->server_context_list.empty())
+ return;
+
+ ::agl_shell_ipc::AppStateResponse app;
+
+ sh->current_app_state.set_app_id(std::string(app_id));
+ sh->current_app_state.set_state(state);
+
+ auto start = sh->server_context_list.begin();
+ while (start != sh->server_context_list.end()) {
+ // hold on if we're still detecting another in-flight writting
+ if (start->second->Writting()) {
+ LOG("skip writing to lister %p\n", static_cast<void *>(start->second));
+ continue;
+ }
+
+ LOG("writing to lister %p\n", static_cast<void *>(start->second));
+ start->second->NextWrite();
+ start++;
+ }
+}
+
+static const struct agl_shell_listener shell_listener = {
+ agl_shell_bound_ok,
+ agl_shell_bound_fail,
+ agl_shell_app_state,
+};
+
+static const struct agl_shell_listener shell_listener_init = {
+ agl_shell_bound_ok_init,
+ agl_shell_bound_fail_init,
+ nullptr,
+};
+
+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) {
+ // bind to at least v3 to get events
+ 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);
+ }
+}
+
+// the purpose of this _init is to make sure we're not the first shell client
+// running to allow the 'main' shell client take over.
+static void
+global_add_init(void *data, struct wl_registry *reg, uint32_t id,
+ const char *interface, uint32_t version)
+{
+
+ struct shell_data_init *sh = static_cast<struct shell_data_init *>(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_init, data);
+ sh->version = 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 const struct wl_registry_listener registry_ext_listener = {
+ global_add_ext,
+ global_remove,
+};
+
+static const struct wl_registry_listener registry_listener = {
+ global_add,
+ global_remove,
+};
+
+static const struct wl_registry_listener registry_listener_init = {
+ global_add_init,
+ global_remove,
+};
+
+static void
+register_shell_ext(struct wl_display *wl_display, struct shell_data *sh)
+{
+ 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 shell_data *sh)
+{
+ 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
+__register_shell_init(void)
+{
+ int ret = 0;
+ struct wl_registry *registry;
+ struct wl_display *wl_display;
+
+ struct shell_data_init *sh = new struct shell_data_init;
+
+ wl_display = wl_display_connect(NULL);
+ registry = wl_display_get_registry(wl_display);
+ sh->wait_for_bound = true;
+ sh->bound_fail = false;
+ sh->bound_ok = false;
+
+ wl_registry_add_listener(registry, &registry_listener_init, sh);
+ wl_display_roundtrip(wl_display);
+
+ if (!sh->shell || sh->version < 3) {
+ ret = -1;
+ goto err;
+ }
+
+ while (ret !=- 1 && sh->wait_for_bound) {
+ ret = wl_display_dispatch(wl_display);
+
+ if (sh->wait_for_bound)
+ continue;
+ }
+
+ ret = sh->bound_fail;
+
+ agl_shell_destroy(sh->shell);
+ wl_display_flush(wl_display);
+err:
+ wl_registry_destroy(registry);
+ wl_display_disconnect(wl_display);
+ delete sh;
+ return ret;
+}
+
+// we expect this client to be up & running *after* the shell client has
+// already set-up panels/backgrounds.
+// this means the very first try to bind to agl_shell we wait for
+// 'bound_fail' event, which would tell us when it's ok to attempt to
+// bind agl_shell_ext, call doas request, then attempt to bind (one
+// more time) to agl_shell but this time wait for 'bound_ok' event.
+void
+register_shell_init(void)
+{
+ struct timespec ts = {};
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 250 * 1000 * 1000; // 250 ms
+
+ // verify if 'bound_fail' was received
+ while (true) {
+
+ int r = __register_shell_init();
+
+ if (r < 0) {
+ LOG("agl-shell extension not found or version too low\n");
+ exit(EXIT_FAILURE);
+ } else if (r == 1) {
+ // we need to get a 'bound_fail' event, if we get a 'bound_ok'
+ // it means we're the first shell to start so wait until the
+ // shell client actually started
+ LOG("Found another shell client running. "
+ "Going further to bind to the agl_shell_ext interface\n");
+ break;
+ }
+
+ LOG("No shell client detected running. Will wait until one starts up...\n");
+ nanosleep(&ts, NULL);
+ }
+
+}
+
+static void
+destroy_shell_data(struct shell_data *sh)
+{
+ 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;
+}
+
+static struct shell_data *
+start_agl_shell_client(void)
+{
+ int ret = 0;
+ struct wl_display *wl_display;
+
+ wl_display = wl_display_connect(NULL);
+
+ struct shell_data *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, sh);
+
+ // check for agl_shell_ext
+ if (!sh->shell_ext) {
+ LOG("Failed to bind to agl_shell_ext interface\n");
+ goto err;
+ }
+
+ if (wl_list_empty(&sh->output_list)) {
+ LOG("Failed get any outputs!\n");
+ goto err;
+ }
+
+ 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) {
+ LOG("Failed to get doas_done event\n");
+ goto err;
+ }
+
+ // bind to agl-shell
+ register_shell(wl_display, sh);
+ 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) {
+ LOG("Failed to get bound_ok event!\n");
+ goto err;
+ }
+
+ LOG("agl_shell/agl_shell_ext interface OK\n");
+
+ return sh;
+err:
+ delete sh;
+ return nullptr;
+}
+
+static void
+start_grpc_server(Shell *aglShell)
+{
+ // instantiante the grpc server
+ std::string server_address(kDefaultGrpcServiceAddress);
+ GrpcServiceImpl service{aglShell};
+
+ 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());
+ LOG("gRPC server listening on %s\n", server_address.c_str());
+
+ server->Wait();
+}
+
+int main(int argc, char **argv)
+{
+ (void) argc;
+ (void) argv;
+ Shell *aglShell;
+ int ret = 0;
+
+ // this blocks until we detect that another shell client started
+ // running
+ register_shell_init();
+
+ struct shell_data *sh = start_agl_shell_client();
+ if (!sh) {
+ LOG("Failed to initialize agl-shell/agl-shell-ext\n");
+ exit(EXIT_FAILURE);
+ }
+
+ std::shared_ptr<struct agl_shell> agl_shell{sh->shell, agl_shell_destroy};
+ aglShell = new Shell(agl_shell, sh);
+
+ std::thread thread(start_grpc_server, aglShell);
+
+ // serve wayland requests
+ while (running && ret != -1) {
+ ret = wl_display_dispatch(sh->wl_display);
+ }
+
+ destroy_shell_data(sh);
+ return 0;
+}
diff --git a/grpc-proxy/main-grpc.h b/grpc-proxy/main-grpc.h
new file mode 100644
index 0000000..282600d
--- /dev/null
+++ b/grpc-proxy/main-grpc.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright © 2022 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#pragma once
+
+#include <cstdio>
+#include <algorithm>
+#include <queue>
+#include <mutex>
+#include <condition_variable>
+#include <wayland-client.h>
+
+#define GRPC_CALLBACK_API_NONEXPERIMENTAL
+
+#include "agl_shell.grpc.pb.h"
+
+// forward declaration created in grpc-async-cb
+class Lister;
+
+struct shell_data {
+ struct wl_display *wl_display;
+ struct agl_shell *shell;
+ struct agl_shell_ext *shell_ext;
+
+ 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 */
+
+ ::agl_shell_ipc::AppStateResponse current_app_state;
+ std::list<std::pair<grpc::CallbackServerContext*, Lister *> > server_context_list;
+};
+
+struct window_output {
+ struct shell_data *shell_data;
+ struct wl_output *output;
+ char *name;
+ struct wl_list link; /** display::output_list */
+};
diff --git a/grpc-proxy/meson.build b/grpc-proxy/meson.build
new file mode 100644
index 0000000..3d7171b
--- /dev/null
+++ b/grpc-proxy/meson.build
@@ -0,0 +1,46 @@
+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@/',
+ '--cpp_out=@BUILD_DIR@',
+ '@INPUT@'])
+
+generated_protoc_sources = protoc_gen.process('agl_shell.proto')
+
+grpc_gen = generator(protoc, \
+ output : ['@BASENAME@.grpc.pb.cc', '@BASENAME@.grpc.pb.h'],
+ arguments : ['--proto_path=@CURRENT_SOURCE_DIR@/',
+ '--grpc_out=@BUILD_DIR@',
+ '--plugin=protoc-gen-grpc=' + grpc_cpp.path(),
+ '@INPUT@'])
+generated_grpc_sources = grpc_gen.process('agl_shell.proto')
+
+grpc_deps = [
+ dependency('protobuf'),
+ dependency('grpc'),
+ dependency('grpc++'),
+ grpcpp_reflection_dep,
+]
+
+srcs = [
+ 'main-grpc.cpp',
+ 'grpc-async-cb.cpp',
+ 'shell.cpp',
+ generated_protoc_sources,
+ generated_grpc_sources,
+ agl_shell_client_protocol_h,
+ agl_shell_protocol_c,
+]
+
+executable(
+ 'agl-shell-grpc-server', srcs,
+ include_directories: [ common_inc ],
+ dependencies: [ grpc_deps, dep_wayland_client, libweston_dep ],
+ install: true,
+ install_dir: dir_module_agl_compositor,
+)
diff --git a/grpc-proxy/shell.cpp b/grpc-proxy/shell.cpp
new file mode 100644
index 0000000..cc6ead6
--- /dev/null
+++ b/grpc-proxy/shell.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright © 2022 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <cstdio>
+#include <ctime>
+#include <algorithm>
+#include <cstring>
+#include <string>
+#include <queue>
+
+#include "main-grpc.h"
+#include "shell.h"
+
+void
+Shell::ActivateApp(const std::string &app_id, const std::string &output_name)
+{
+ struct window_output *woutput, *w_output;
+ struct agl_shell *shell = this->m_shell.get();
+
+ woutput = nullptr;
+ w_output = nullptr;
+
+ wl_list_for_each(woutput, &m_shell_data->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(m_shell_data->output_list.prev,
+ w_output, link);
+
+ agl_shell_activate_app(shell, app_id.c_str(), w_output->output);
+ wl_display_flush(m_shell_data->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;
+}
diff --git a/grpc-proxy/shell.h b/grpc-proxy/shell.h
new file mode 100644
index 0000000..03a4a87
--- /dev/null
+++ b/grpc-proxy/shell.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright © 2022 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#pragma once
+
+#include <memory>
+
+#include "agl-shell-client-protocol.h"
+
+#include "main-grpc.h"
+
+class Shell {
+public:
+ std::shared_ptr<struct agl_shell> m_shell;
+ struct shell_data *m_shell_data;
+
+ Shell(std::shared_ptr<struct agl_shell> shell,
+ struct shell_data *sh_data) :
+ m_shell(shell), m_shell_data(sh_data) { }
+ 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/meson.build b/meson.build
index 0958f06..26f70fd 100644
--- a/meson.build
+++ b/meson.build
@@ -296,3 +296,7 @@ install_data(
common_inc = [ include_directories('src'), include_directories('.') ]
subdir('clients')
+
+if get_option('grpc-proxy')
+ subdir('grpc-proxy')
+endif
diff --git a/meson_options.txt b/meson_options.txt
index dd1f3c0..1e54773 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -5,3 +5,10 @@ option(
value: 'allow-all',
description: 'Default policy when no specific policy was set'
)
+
+option(
+ 'grpc-proxy',
+ type: 'boolean',
+ value: true,
+ description: 'Build gRPC proxy which exposes some of the agl-shell protocol over a gRPC API'
+)