AglShellGrpcClient: Add activation with gRPC proxy 07/29307/2
authorMarius Vlad <marius.vlad@collabora.com>
Wed, 11 Oct 2023 09:08:16 +0000 (12:08 +0300)
committerMarius Vlad <marius.vlad@collabora.com>
Wed, 25 Oct 2023 21:41:50 +0000 (00:41 +0300)
This follow-ups in footsteps of flutter-homescreen to have activation
using gRPC. Note that setting up wayland surfaces is still need to
have the agl-shell protocol available. In Qt this is managed directly
by Qt/QPA while on other toolkits this happens at a lower level
(flutter-auto or chromium CEF).

With it, create a listening thread for gRRC events in order to perform
the initial start + activate sequence.

Bug-AGL: SPEC-4912
Signed-off-by: Marius Vlad <marius.vlad@collabora.com>
Change-Id: I019d0e7944dde02c84b2841e22d3971f226d610a

homescreen/meson.build
homescreen/proto/agl_shell.proto [new file with mode: 0644]
homescreen/src/AglShellGrpcClient.cpp [new file with mode: 0644]
homescreen/src/AglShellGrpcClient.h [new file with mode: 0644]
homescreen/src/homescreenhandler.cpp
homescreen/src/homescreenhandler.h
homescreen/src/main.cpp

index db2409d..50c644c 100644 (file)
@@ -9,6 +9,10 @@ dep_qtappfw = [
     dependency('qtappfw-applauncher')
 ]
 
+grpcpp_reflection_dep = cpp.find_library('grpc++_reflection')
+protoc = find_program('protoc')
+grpc_cpp = find_program('grpc_cpp_plugin')
+
 qt_defines = []
 qpa_header_path = join_paths(qt5_dep.version(), 'QtGui')
 qpa_header = join_paths(qpa_header_path, 'qpa/qplatformnativeinterface.h')
@@ -24,10 +28,35 @@ prog_scanner = find_program('wayland-scanner')
 agl_compositor_dep = dependency('agl-compositor-0.0.21-protocols')
 dir_agl_compositor_base = agl_compositor_dep.get_variable(pkgconfig: 'pkgdatadir')
 
+protoc_gen = generator(protoc, \
+                       output : ['@BASENAME@.pb.cc', '@BASENAME@.pb.h'],
+                       arguments : ['--proto_path=@CURRENT_SOURCE_DIR@/proto',
+                         '--cpp_out=@BUILD_DIR@',
+                         '@INPUT@'])
+
+generated_protoc_sources = protoc_gen.process('proto/agl_shell.proto')
+
+grpc_gen = generator(protoc, \
+                     output : ['@BASENAME@.grpc.pb.cc', '@BASENAME@.grpc.pb.h'],
+                     arguments : ['--proto_path=@CURRENT_SOURCE_DIR@/proto',
+                       '--grpc_out=@BUILD_DIR@',
+                       '--plugin=protoc-gen-grpc=' + grpc_cpp.path(),
+                       '@INPUT@'])
+generated_grpc_sources = grpc_gen.process('proto/agl_shell.proto')
+
+grpc_deps = [
+    dependency('protobuf'),
+    dependency('grpc'),
+    dependency('grpc++'),
+    grpcpp_reflection_dep,
+]
+
+
 homescreen_dep = [
     qt5_dep,
     dep_wayland_client,
     dep_qtappfw,
+    grpc_deps
 ]
 
 homescreen_resources = [
@@ -83,6 +112,7 @@ homescreen_src_headers = [
   'src/statusbarmodel.h',
   'src/statusbarserver.h',
   'src/homescreenhandler.h',
+  'src/AglShellGrpcClient.h',
   'src/shell.h'
 ]
 
@@ -96,9 +126,12 @@ homescreen_src = [
   'src/applicationlauncher.cpp',
   'src/mastervolume.cpp',
   'src/homescreenhandler.cpp',
+  'src/AglShellGrpcClient.cpp',
   'src/main.cpp',
   agl_shell_client_protocol_h,
-  agl_shell_protocol_c
+  agl_shell_protocol_c,
+  generated_protoc_sources,
+  generated_grpc_sources
 ]
 
 executable('homescreen', homescreen_src, resource_files, moc_files,
diff --git a/homescreen/proto/agl_shell.proto b/homescreen/proto/agl_shell.proto
new file mode 100644 (file)
index 0000000..c4f3dfe
--- /dev/null
@@ -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 {
+};
diff --git a/homescreen/src/AglShellGrpcClient.cpp b/homescreen/src/AglShellGrpcClient.cpp
new file mode 100644 (file)
index 0000000..6f4ff24
--- /dev/null
@@ -0,0 +1,186 @@
+//include stuff here
+#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 "AglShellGrpcClient.h"
+#include "agl_shell.grpc.pb.h"
+
+namespace {
+       const char kDefaultGrpcServiceAddress[] = "127.0.0.1:14005";
+}
+
+GrpcClient::GrpcClient()
+{
+       auto channel = grpc::CreateChannel(kDefaultGrpcServiceAddress,
+                       grpc::InsecureChannelCredentials());
+
+       // init the stub here
+       m_stub = agl_shell_ipc::AglShellManagerService::NewStub(channel);
+       reader = new Reader(m_stub.get());
+}
+
+
+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)
+{
+       reader->AppStatusState(callback);
+}
+
+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/homescreen/src/AglShellGrpcClient.h b/homescreen/src/AglShellGrpcClient.h
new file mode 100644 (file)
index 0000000..ff190f7
--- /dev/null
@@ -0,0 +1,110 @@
+#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);
+
+class Reader : public grpc::ClientReadReactor<::agl_shell_ipc::AppStateResponse> {
+public:
+       Reader(agl_shell_ipc::AglShellManagerService::Stub *stub)
+               : m_stub(stub)
+       {
+       }
+
+       void AppStatusState(Callback callback)
+       {
+               ::agl_shell_ipc::AppStateRequest request;
+
+               // set up the callback
+               m_callback = callback;
+               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);
+
+                       // 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;
+       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();
+       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);
+       grpc::Status Wait();
+
+private:
+       Reader *reader;
+       std::unique_ptr<agl_shell_ipc::AglShellManagerService::Stub> m_stub;
+};
+
index 9353713..d81e22f 100644 (file)
@@ -5,6 +5,7 @@
  */
 
 #include <QGuiApplication>
+#include <QScreen>
 #include <QFileInfo>
 #include <functional>
 
@@ -20,12 +21,10 @@ QScreen *find_screen(const char *output);
 // a user session by systemd
 #define LAUNCHER_APP_ID          "launcher"
 
-static struct wl_output *
-getWlOutput(QPlatformNativeInterface *native, QScreen *screen);
 
-HomescreenHandler::HomescreenHandler(Shell *_aglShell, ApplicationLauncher *launcher, QObject *parent) :
-       QObject(parent),
-       aglShell(_aglShell)
+HomescreenHandler::HomescreenHandler(ApplicationLauncher *launcher, GrpcClient *_client, QObject *parent) :
+       QObject(parent)
+       , m_grpc_client(_client)
 {
        mp_launcher = launcher;
        mp_applauncher_client = new AppLauncherClient();
@@ -43,16 +42,10 @@ HomescreenHandler::HomescreenHandler(Shell *_aglShell, ApplicationLauncher *laun
 
 HomescreenHandler::~HomescreenHandler()
 {
+       delete m_grpc_client;
        delete mp_applauncher_client;
 }
 
-static struct wl_output *
-getWlOutput(QPlatformNativeInterface *native, QScreen *screen)
-{
-       void *output = native->nativeResourceForScreen("output", screen);
-       return static_cast<struct ::wl_output*>(output);
-}
-
 void HomescreenHandler::tapShortcut(QString app_id)
 {
        HMI_DEBUG("HomeScreen","tapShortcut %s", app_id.toStdString().c_str());
@@ -92,18 +85,15 @@ void HomescreenHandler::addAppToStack(const QString& app_id)
 
 void HomescreenHandler::activateApp(const QString& app_id)
 {
-       struct agl_shell *agl_shell = aglShell->shell.get();
-       QScreen *tmp_screen = qApp->screens().first();
-       QPlatformNativeInterface *native = qApp->platformNativeInterface();
-       struct wl_output *mm_output = nullptr;
+       QScreen *default_screen = qApp->screens().first();
+       std::string default_output_name;
 
-       if (!tmp_screen) {
-               HMI_DEBUG("HomeScreen", "No output found to activate on!\n");
+       if (!default_screen) {
+               HMI_DEBUG("HomeScreen", "No default output found to activate on!\n");
        } else {
-               mm_output = getWlOutput(native, tmp_screen);
-
-               HMI_DEBUG("HomeScreen", "Activating app_id %s by default on output %p\n",
-                               app_id.toStdString().c_str(), mm_output);
+               default_output_name = default_screen->name().toStdString();
+               HMI_DEBUG("HomeScreen", "Activating app_id %s by default on output %s\n",
+                               app_id.toStdString().c_str(), default_output_name.c_str());
        }
 
        if (mp_launcher) {
@@ -155,27 +145,25 @@ void HomescreenHandler::activateApp(const QString& app_id)
                        HMI_DEBUG("HomeScreen", "Found a stream remoting output %s to activate application %s on",
                                  new_remote_output.c_str(),
                                  app_id.toStdString().c_str());
+                       default_output_name = new_remote_output;
                }
 
-               mm_output = getWlOutput(native, screen);
                pending_app_list.erase(iter);
-
                HMI_DEBUG("HomeScreen", "For application %s found another "
                                "output to activate %s\n",
                                app_id.toStdString().c_str(),
-                               output_name.toStdString().c_str());
+                               default_output_name.c_str());
        }
 
-       if (!mm_output) {
+       if (default_output_name.empty()) {
                HMI_DEBUG("HomeScreen", "No suitable output found for activating %s",
                                app_id.toStdString().c_str());
                return;
        }
 
-       HMI_DEBUG("HomeScreen", "Activating application %s",
-                       app_id.toStdString().c_str());
-
-       agl_shell_activate_app(agl_shell, app_id.toStdString().c_str(), mm_output);
+       HMI_DEBUG("HomeScreen", "Activating application %s on output %s",
+                       app_id.toStdString().c_str(), default_output_name.c_str());
+       m_grpc_client->ActivateApp(app_id.toStdString(), default_output_name);
 }
 
 void HomescreenHandler::deactivateApp(const QString& app_id)
index 9a659a6..d95963b 100644 (file)
@@ -12,8 +12,7 @@
 
 #include "applicationlauncher.h"
 #include "AppLauncherClient.h"
-
-#include "shell.h"
+#include "AglShellGrpcClient.h"
 
 using namespace std;
 
@@ -21,7 +20,7 @@ class HomescreenHandler : public QObject
 {
        Q_OBJECT
 public:
-       explicit HomescreenHandler(Shell *aglShell, ApplicationLauncher *launcher = 0, QObject *parent = 0);
+       explicit HomescreenHandler(ApplicationLauncher *launcher = 0, GrpcClient *_client = nullptr, QObject *parent = 0);
        ~HomescreenHandler();
 
        Q_INVOKABLE void tapShortcut(QString application_id);
@@ -42,9 +41,7 @@ public slots:
 private:
        ApplicationLauncher *mp_launcher;
        AppLauncherClient *mp_applauncher_client;
-
-       Shell *aglShell;
-
+       GrpcClient *m_grpc_client;
 };
 
 #endif // HOMESCREENHANDLER_H
index d0bd2c3..b004fd5 100644 (file)
@@ -3,6 +3,7 @@
  * Copyright (C) 2016, 2017 Mentor Graphics Development (Deutschland) GmbH
  * Copyright (c) 2017, 2018 TOYOTA MOTOR CORPORATION
  * Copyright (c) 2022 Konsulko Group
+ * Copyright (c) 2023 Collabora, Ltd.
  */
 
 #include <QGuiApplication>
@@ -32,6 +33,9 @@
 #include "agl-shell-client-protocol.h"
 #include "shell.h"
 
+#include <thread>
+#include "AglShellGrpcClient.h"
+
 #ifndef MIN
 #define MIN(a, b) (((a) < (b)) ? (a) : (b))
 #endif
@@ -361,6 +365,20 @@ load_agl_shell_app(QPlatformNativeInterface *native, QQmlApplicationEngine *engi
        });
 }
 
+static void
+run_in_thread(GrpcClient *client)
+{
+        grpc::Status status = client->Wait();
+}
+
+static void
+app_status_callback(::agl_shell_ipc::AppStateResponse app_response)
+{
+       std::cout << " >> AppStateResponse app_id " <<
+               app_response.app_id() << ", with state " <<
+               app_response.state() << std::endl;
+}
+
 int main(int argc, char *argv[])
 {
        setenv("QT_QPA_PLATFORM", "wayland", 1);
@@ -416,7 +434,6 @@ int main(int argc, char *argv[])
 
 
        std::shared_ptr<struct agl_shell> agl_shell{shell_data.shell, agl_shell_destroy};
-       Shell *aglShell = new Shell(agl_shell, &app);
 
        // Import C++ class to QML
        qmlRegisterType<StatusBarModel>("HomeScreen", 1, 0, "StatusBarModel");
@@ -425,7 +442,13 @@ int main(int argc, char *argv[])
        ApplicationLauncher *launcher = new ApplicationLauncher();
        launcher->setCurrent(QStringLiteral("launcher"));
 
-       HomescreenHandler* homescreenHandler = new HomescreenHandler(aglShell, launcher);
+       GrpcClient *client = new GrpcClient();
+
+       // create a new thread to listner for gRPC events
+       std::thread th = std::thread(run_in_thread, client);
+       client->AppStatusState(app_status_callback);
+
+       HomescreenHandler* homescreenHandler = new HomescreenHandler(launcher, client);
        shell_data.homescreenHandler = homescreenHandler;
 
        QQmlApplicationEngine engine;
@@ -436,9 +459,6 @@ int main(int argc, char *argv[])
        context->setContextProperty("weather", new Weather());
        context->setContextProperty("bluetooth", new Bluetooth(false, context));
 
-       // We add it here even if we don't use it
-       context->setContextProperty("shell", aglShell);
-
        load_agl_shell_app(native, &engine, shell_data.shell,
                           screen_name, is_demo_val);