app: Move build to meson and replace agl-shell-desktop 77/30577/3
authorMarius Vlad <marius.vlad@collabora.com>
Wed, 20 Nov 2024 14:05:05 +0000 (16:05 +0200)
committerMarius Vlad <marius.vlad@collabora.com>
Tue, 26 Nov 2024 14:33:56 +0000 (16:33 +0200)
This is a bigger change to move the from Cmake to meson and
use gPRC instead of agl-shell-desktop protocol.

Bug-AGL: SPEC-5300, SPEC-5301
Signed-off-by: Marius Vlad <marius.vlad@collabora.com>
Change-Id: Ib649b7fd38eef5653bc401a8eb159882a2f41e6b

README.md
app/AglShellGrpcClient.cpp [new file with mode: 0644]
app/AglShellGrpcClient.h [new file with mode: 0644]
app/CMakeLists.txt [deleted file]
app/agl_shell.proto [new file with mode: 0644]
app/main.cpp
app/meson.build [new file with mode: 0644]
app/protocol/agl-shell-desktop.xml [deleted file]
meson.build [moved from CMakeLists.txt with 57% similarity]

index 17a1bba..43f2eee 100644 (file)
--- a/README.md
+++ b/README.md
@@ -3,16 +3,15 @@ xdg-cluster-receiver
 
 This is a variant of the cluster-demo-receiver but without any toolkit
 involvement, using wayland-protocols (to gain access to XDG-Shell) and
-agl-shell* private extensions provided by the compositor.
+gRPC to perform window management operations.
 
 We use XDG-Shell to create a top-level XDG window and set an application id for
-it. We use agl-shell-desktop to be able to position indepedently the surface
-on top of the cluster-dashbboard application, and in the same time specify
-a bounding box.
+it. We use gRPC agl_shell protocol to be able to position indepedently the
+surface on top of the cluster-dashbboard application.
 
 Underneath, waylandsink requires a parent surface (wl_surface) as to create a
 sub-subsurface where it will draw, on its own, the incoming stream.
 
 We don't pass out that parent surface to the compositor, but instead of use the
-app_id to identify applications, that is why it is import to set, for the parent
-surface an application id.
+app_id to identify applications, that is why it is import to set, for the
+parent surface an application id.
diff --git a/app/AglShellGrpcClient.cpp b/app/AglShellGrpcClient.cpp
new file mode 100644 (file)
index 0000000..c7b76d5
--- /dev/null
@@ -0,0 +1,225 @@
+//include stuff here
+#include <cstdio>
+#include <time.h>
+
+#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()
+{
+       int retries = 10;
+       struct timespec ts;
+
+       if (clock_gettime(CLOCK_MONOTONIC, &ts))
+               throw std::runtime_error("Failed CLOCK_MONOTONIC");
+
+       auto channel = grpc::CreateChannel(kDefaultGrpcServiceAddress,
+                       grpc::InsecureChannelCredentials());
+
+       ts.tv_nsec = 500 * 1000 * 1000;
+       ts.tv_sec = 0;
+
+       auto state = channel->GetState(true);
+       // artificial delay otherwise to be sure req calls succeed
+       while (retries-- > 0) {
+               state = channel->GetState(true);
+               if (state == GRPC_CHANNEL_READY)
+                       break;
+               nanosleep(&ts, NULL);
+       }
+
+       if (state != GRPC_CHANNEL_READY)
+               fprintf(stderr, "WARNING: channel not in ready state: %d\n", state);
+
+       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();
+}
+
+
+bool
+GrpcClient::SetAppSplit(const std::string& app_id, uint32_t orientation,
+                       int32_t width, int32_t sticky, const std::string& output_name)
+{
+       agl_shell_ipc::SplitRequest request;
+
+       request.set_app_id(app_id);
+       request.set_output_name(output_name);
+       request.set_tile_orientation(orientation);
+       request.set_width(width);
+       request.set_sticky(sticky);
+
+       grpc::ClientContext context;
+       ::agl_shell_ipc::SplitResponse reply;
+
+       grpc::Status status = m_stub->SetAppSplit(&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/app/AglShellGrpcClient.h b/app/AglShellGrpcClient.h
new file mode 100644 (file)
index 0000000..f66b44c
--- /dev/null
@@ -0,0 +1,111 @@
+#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);
+       bool SetAppSplit(const std::string& app_id, uint32_t orientation,
+                        int32_t width, int32_t sticky, const std::string& output_name);
+       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;
+};
+
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
deleted file mode 100644 (file)
index 4ac3455..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-###########################################################################
-# Copyright 2018,2022 Konsulko Group
-# Copyright 2020 Collabora, Ltd.
-#
-# Author: Scott Murray <scott.murray@konsulko.com>
-# Author: Marius Vlad <marius.vlad@collabora.com>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-###########################################################################
-
-
-project(xdg-cluster-receiver VERSION 2.0.0 LANGUAGES CXX)
-
-if(CMAKE_VERSION VERSION_LESS "3.7.0")
-    set(CMAKE_INCLUDE_CURRENT_DIR ON)
-endif()
-set(OE_QMAKE_PATH_EXTERNAL_HOST_BINS $ENV{OE_QMAKE_PATH_HOST_BINS})
-
-set(CMAKE_CXX_STANDARD 14)
-set(CMAKE_CXX_STANDARD_REQUIRED ON)
-
-find_package(PkgConfig REQUIRED)
-find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner)
-
-pkg_check_modules(AGL_COMPOSITOR_PROTOCOLS REQUIRED agl-compositor-0.0.24-protocols)
-pkg_get_variable(AGL_COMPOSITOR_PROTOCOLS_PKGDATADIR agl-compositor-0.0.24-protocols pkgdatadir)
-set(AGL_COMPOSITOR_PROTOCOLS_PATH ${AGL_COMPOSITOR_PROTOCOLS_PKGDATADIR})
-
-add_custom_command(
-       OUTPUT  agl-shell-desktop-client-protocol.h
-       COMMAND ${WAYLAND_SCANNER_EXECUTABLE} client-header
-       < ${AGL_COMPOSITOR_PROTOCOLS_PATH}/agl-shell-desktop.xml
-       > ${CMAKE_SOURCE_DIR}/app/agl-shell-desktop-client-protocol.h
-       DEPENDS ${AGL_COMPOSITOR_PROTOCOLS_PATH}/agl-shell-desktop.xml
-)
-
-add_custom_command(
-       OUTPUT  ${CMAKE_BINARY_DIR}/app/agl-shell-desktop-client-protocol.h
-       COMMAND ${WAYLAND_SCANNER_EXECUTABLE} client-header
-       < ${AGL_COMPOSITOR_PROTOCOLS_PATH}/agl-shell-desktop.xml
-       > ${CMAKE_SOURCE_DIR}/app/agl-shell-desktop-client-protocol.h
-       DEPENDS ${AGL_COMPOSITOR_PROTOCOLS_PATH}/agl-shell-desktop.xml
-)
-
-add_custom_command(
-       OUTPUT  agl-shell-desktop-protocol.c
-       COMMAND ${WAYLAND_SCANNER_EXECUTABLE} code
-       < ${AGL_COMPOSITOR_PROTOCOLS_PATH}/agl-shell-desktop.xml
-       > ${CMAKE_BINARY_DIR}/app/agl-shell-desktop-protocol.c
-       DEPENDS ${AGL_COMPOSITOR_PROTOCOLS_PATH}/agl-shell-desktop.xml
-)
-
-pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0)
-pkg_check_modules(GSTREAMER_PLUGINS_BASE REQUIRED gstreamer-plugins-base-1.0)
-pkg_check_modules(GSTREAMER_VIDEO REQUIRED gstreamer-video-1.0)
-pkg_check_modules(GSTREAMER_PLUGINS_BAD REQUIRED gstreamer-plugins-bad-1.0)
-
-pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client)
-pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols>=1.18)
-pkg_get_variable(WAYLAND_PROTOCOLS_BASE wayland-protocols pkgdatadir)
-
-add_custom_command(
-       OUTPUT  xdg-shell-client-protocol.h
-       COMMAND ${WAYLAND_SCANNER_EXECUTABLE} client-header
-       < ${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml
-       > ${CMAKE_SOURCE_DIR}/app/xdg-shell-client-protocol.h
-       DEPENDS ${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml
-)
-
-add_custom_command(
-       OUTPUT  ${CMAKE_BINARY_DIR}/app/xdg-shell-client-protocol.h
-       COMMAND ${WAYLAND_SCANNER_EXECUTABLE} client-header
-       < ${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml
-       > ${CMAKE_SOURCE_DIR}/app/xdg-shell-client-protocol.h
-       DEPENDS ${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml
-)
-
-add_custom_command(
-       OUTPUT  xdg-shell-protocol.c
-       COMMAND ${WAYLAND_SCANNER_EXECUTABLE} code
-       < ${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml
-       > ${CMAKE_BINARY_DIR}/app/xdg-shell-protocol.c
-       DEPENDS ${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml
-)
-
-add_executable(${PROJECT_NAME}
-       main.cpp
-       agl-shell-desktop-protocol.c
-       agl-shell-desktop-client-protocol.h
-       xdg-shell-protocol.c
-       xdg-shell-client-protocol.h
-       ${RESOURCES}
-)
-
-include_directories(
-       "${GSTREAMER_INCLUDE_DIRS}"
-       "${GSTREAMER_PLUGINS_BASE_INCLUDE_DIRS}"
-       "${GSTREAMER_PLUGINS_BAD_INCLUDE_DIRS}"
-       "${GSTREAMER_VIDEO_INCLUDE_DIRS}"
-)
-
-target_link_libraries(${PROJECT_NAME}
-       ${GSTREAMER_LIBRARIES}
-       "${GSTREAMER_PLUGINS_BASE_LIBRARIES}"
-       "${GSTREAMER_PLUGINS_BAD_LIBRARIES}"
-       "${GSTREAMER_VIDEO_LIBRARIES}"
-       ${WAYLAND_CLIENT_LIBRARIES}
-       -lgstwayland-1.0
-)
-
-install(TARGETS ${PROJECT_NAME} DESTINATION bin)
diff --git a/app/agl_shell.proto b/app/agl_shell.proto
new file mode 100644 (file)
index 0000000..d38d896
--- /dev/null
@@ -0,0 +1,113 @@
+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;
+       int32 width = 3;
+       int32 sticky = 4;
+       string output_name = 5;
+}
+
+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 {
+};
index 2d081c9..3953646 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2020 Collabora, Ltd.
+ * Copyright © 2020, 2024 Collabora, Ltd.
  *
  * Permission is hereby granted, free of charge, to any person obtaining
  * a copy of this software and associated documentation files (the
@@ -41,7 +41,6 @@
 #include <glib.h>
 
 #include "xdg-shell-client-protocol.h"
-#include "agl-shell-desktop-client-protocol.h"
 #include "zalloc.h"
 
 #include <gst/gst.h>
@@ -49,6 +48,9 @@
 #include <gst/video/videooverlay.h>
 #include <gst/wayland/wayland.h>
 
+#include "AglShellGrpcClient.h"
+
+
 #if !GST_CHECK_VERSION(1, 22, 0)
 #define gst_is_wl_display_handle_need_context_message gst_is_wayland_display_handle_need_context_message
 #define gst_wl_display_handle_context_new gst_wayland_display_handle_context_new
@@ -84,7 +86,6 @@ struct display {
        } output_data;
 
        struct xdg_wm_base *wm_base;
-       struct agl_shell_desktop *agl_shell_desktop;
 
        int has_xrgb;
 };
@@ -127,7 +128,7 @@ static void
 redraw(void *data, struct wl_callback *callback, uint32_t time);
 
 static void
-paint_pixels(void *image, int padding, int width, int height, uint32_t time)
+paint_pixels(void *image, int width, int height)
 {
        memset(image, 0x00, width * height * 4);
 }
@@ -136,6 +137,8 @@ static void
 buffer_release(void *data, struct wl_buffer *buffer)
 {
        struct buffer *mybuf = static_cast<struct buffer *>(data);
+       (void) buffer;
+
        mybuf->busy = 0;
 }
 
@@ -359,6 +362,7 @@ redraw(void *data, struct wl_callback *callback, uint32_t time)
 {
         struct window *window = static_cast<struct window *>(data);
         struct buffer *buffer;
+       (void) time;
 
         buffer = get_next_buffer(window);
         if (!buffer) {
@@ -369,7 +373,7 @@ redraw(void *data, struct wl_callback *callback, uint32_t time)
         }
 
        // do the actual painting
-       paint_pixels(buffer->shm_data, 0x0, window->width, window->height, time);
+       paint_pixels(buffer->shm_data, window->width, window->height);
 
         wl_surface_attach(window->surface, buffer->buffer, 0, 0);
         wl_surface_damage(window->surface, 0, 0, window->width, window->height);
@@ -388,6 +392,7 @@ static void
 shm_format(void *data, struct wl_shm *wl_shm, uint32_t format)
 {
        struct display *d = static_cast<struct display *>(data);
+       (void) wl_shm;
 
        if (format == WL_SHM_FORMAT_XRGB8888)
                d->has_xrgb = true;
@@ -400,6 +405,7 @@ static const struct wl_shm_listener shm_listener = {
 static void
 xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial)
 {
+       (void) data;
         xdg_wm_base_pong(shell, serial);
 }
 
@@ -429,6 +435,7 @@ display_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags,
                    int width, int height, int refresh)
 {
        struct display *d = static_cast<struct display *>(data);
+       (void) refresh;
 
        if (wl_output == d->wl_output && (flags & WL_OUTPUT_MODE_CURRENT)) {
                d->output_data.width = width;
@@ -454,38 +461,29 @@ display_handle_done(void *data, struct wl_output *wl_output)
        (void) wl_output;
 }
 
-static const struct wl_output_listener output_listener = {
-       display_handle_geometry,
-       display_handle_mode,
-       display_handle_done,
-       display_handle_scale
-};
-
 static void
-application_id(void *data, struct agl_shell_desktop *agl_shell_desktop,
-               const char *app_id)
+display_handle_name(void *data, struct wl_output *wl_output, const char *name)
 {
        (void) data;
-       (void) agl_shell_desktop;
-       (void) app_id;
+       (void) wl_output;
+       (void) name;
 }
 
 static void
-application_id_state(void *data, struct agl_shell_desktop *agl_shell_desktop,
-                    const char *app_id, const char *app_data,
-                    uint32_t app_state, uint32_t app_role)
+display_handle_desc(void *data, struct wl_output *wl_output, const char *desc)
 {
-        (void) data;
-        (void) app_data;
-        (void) agl_shell_desktop;
-        (void) app_id;
-        (void) app_state;
-        (void) app_role;
+       (void) data;
+       (void) wl_output;
+       (void) desc;
 }
 
-static const struct agl_shell_desktop_listener agl_shell_desktop_listener = {
-        application_id,
-        application_id_state,
+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_desc,
 };
 
 static void
@@ -493,6 +491,7 @@ registry_handle_global(void *data, struct wl_registry *registry, uint32_t id,
                       const char *interface, uint32_t version)
 {
        struct display *d = static_cast<struct display *>(data);
+       (void) version;
 
        if (strcmp(interface, "wl_compositor") == 0) {
                d->wl_compositor =
@@ -506,12 +505,6 @@ registry_handle_global(void *data, struct wl_registry *registry, uint32_t id,
                d->shm = static_cast<struct wl_shm *>(wl_registry_bind(registry,
                                id, &wl_shm_interface, 1));
                wl_shm_add_listener(d->shm, &shm_listener, d);
-       } else if (strcmp(interface, "agl_shell_desktop") == 0) {
-               d->agl_shell_desktop = static_cast<struct agl_shell_desktop *>(wl_registry_bind(registry, id,
-                                                       &agl_shell_desktop_interface, 1));
-               /* as an example, show how to register for events from the compositor */
-               agl_shell_desktop_add_listener(d->agl_shell_desktop,
-                                              &agl_shell_desktop_listener, d);
        } else if (strcmp(interface, "wl_output") == 0) {
                d->wl_output = static_cast<struct wl_output *>(wl_registry_bind(registry, id,
                                             &wl_output_interface, 1));
@@ -536,6 +529,7 @@ static const struct wl_registry_listener registry_listener = {
 static void
 error_cb(GstBus *bus, GstMessage *msg, gpointer user_data)
 {
+       (void) bus;
        struct cluster_receiver_data *d =
                static_cast<struct cluster_receiver_data *>(user_data);
 
@@ -558,6 +552,7 @@ error_cb(GstBus *bus, GstMessage *msg, gpointer user_data)
 static GstBusSyncReply
 bus_sync_handler(GstBus *bus, GstMessage *message, gpointer user_data)
 {
+       (void) bus;
        struct cluster_receiver_data *d =
                static_cast<struct cluster_receiver_data *>(user_data);
 
@@ -623,6 +618,7 @@ handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
 {
        struct window *window = static_cast<struct window *>(data);
        uint32_t *p;
+       (void) xdg_toplevel;
 
        window->fullscreen = 0;
        window->maximized = 0;
@@ -670,12 +666,37 @@ handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
 static void
 handle_xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel)
 {
+       (void) xdg_toplevel;
+       (void) data;
        running = 0;
 }
 
+static void
+handle_xdg_toplevel_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel,
+                                     int32_t width, int32_t height)
+{
+       (void) data;
+       (void) xdg_toplevel;
+       (void) width;
+       (void) height;
+}
+
+
+static void
+handle_xdg_toplevel_wm_caps(void *data, struct xdg_toplevel *xdg_toplevel, 
+                           struct wl_array *caps)
+{
+       (void) data;
+       (void) xdg_toplevel;
+       (void) caps;
+}
+
+
 static const struct xdg_toplevel_listener xdg_toplevel_listener = {
        handle_xdg_toplevel_configure,
        handle_xdg_toplevel_close,
+       handle_xdg_toplevel_configure_bounds,
+       handle_xdg_toplevel_wm_caps,
 };
 
 static struct window *
@@ -737,12 +758,19 @@ destroy_window(struct window *window)
 static void
 signal_int(int sig, siginfo_t *si, void *_unused)
 {
+       (void) sig;
+       (void) _unused;
+       (void) si;
+
        running = 0;
 }
 
 static struct display *
 create_display(int argc, char *argv[])
 {
+       (void) argc;
+       (void) argv;
+
        struct display *display;
 
        display = static_cast<struct display *>(zalloc(sizeof(*display)));
@@ -764,11 +792,6 @@ create_display(int argc, char *argv[])
                return NULL;
        }
 
-       if (display->agl_shell_desktop == NULL) {
-               fprintf(stderr, "No agl_shell extension present\n");
-               return NULL;
-       }
-
        wl_display_roundtrip(display->wl_display);
 
        if (!display->has_xrgb) {
@@ -788,9 +811,6 @@ destroy_display(struct display *display)
        if (display->wm_base)
                xdg_wm_base_destroy(display->wm_base);
 
-       if (display->agl_shell_desktop)
-               agl_shell_desktop_destroy(display->agl_shell_desktop);
-
        if (display->wl_compositor)
                wl_compositor_destroy(display->wl_compositor);
 
@@ -800,42 +820,42 @@ destroy_display(struct display *display)
        free(display);
 }
 
-void read_config(void)
+void
+read_config(void)
 {
        GKeyFile *conf_file;
        gchar *value;
+       GError *err = NULL;
+
+       bool ret;
+       int n;
+       unsigned width, height, x, y;
 
        // Load settings from configuration file if it exists
        conf_file = g_key_file_new();
-       if(conf_file &&
-          g_key_file_load_from_dirs(conf_file,
-                                    "AGL.conf",
-                                    (const gchar**) g_get_system_config_dirs(),
-                                    NULL,
-                                    G_KEY_FILE_KEEP_COMMENTS,
-                                    NULL) == TRUE) {
-               GError *err = NULL;
-               value = g_key_file_get_string(conf_file,
-                                             "receiver",
-                                             "geometry",
-                                             &err);
-               if(value) {
-                       int n;
-                       unsigned width, height, x, y;
-                       n = sscanf(value, "%ux%u+%u,%u", &width, &height, &x, &y);
-                       if (n == 4) {
-                               g_window_width = width;
-                               g_window_height = height;
-                               g_window_pos_x = x;
-                               g_window_pos_y = y;
-                               printf("Using window geometry %dx%d+%d,%d",
-                                      g_window_width, g_window_height, g_window_pos_x, g_window_pos_y);
-                       } else {
-                               fprintf(stderr, "Invalid value for \"geometry\" key!");
-                       }
-               } else {
-                       fprintf(stderr, "Invalid value for \"geometry\" key!");
-               }
+
+       ret = g_key_file_load_from_dirs(conf_file, "AGL.conf",
+                                       (const gchar**) g_get_system_config_dirs(),
+                                       NULL, G_KEY_FILE_KEEP_COMMENTS, NULL);
+       if (!ret)
+               return;
+
+       value = g_key_file_get_string(conf_file, "receiver", "geometry", &err);
+       if (!value) {
+               return;
+       }
+
+       n = sscanf(value, "%ux%u+%u,%u", &width, &height, &x, &y);
+       if (n == 4) {
+               g_window_width = width;
+               g_window_height = height;
+               g_window_pos_x = x;
+               g_window_pos_y = y;
+               fprintf(stdout, "Using window geometry %dx%d+%d,%d\n",
+                               g_window_width, g_window_height,
+                               g_window_pos_x, g_window_pos_y);
+       } else {
+               fprintf(stderr, "Invalid value for \"geometry\" key\n!");
        }
 }
 
@@ -847,7 +867,8 @@ int main(int argc, char *argv[])
        struct display *display;
        struct window *window;
 
-       std::string role = "receiver";
+       std::string role = "cluster-receiver";
+       GrpcClient *client = new GrpcClient();
 
        sa.sa_sigaction = signal_int;
        sigemptyset(&sa.sa_mask);
@@ -875,20 +896,17 @@ int main(int argc, char *argv[])
        if (!display)
                return -1;
 
-       // this will set-up the x and y position and a bounding box that will
-       // be used to clip out the surface. Note, that in this example, the
-       // surface area is the same as the bounding box.
-       agl_shell_desktop_set_app_property(display->agl_shell_desktop, role.c_str(),
-                                          AGL_SHELL_DESKTOP_APP_ROLE_POPUP,
-                                          g_window_pos_x, g_window_pos_y,
-                                          0, 0, g_window_width, g_window_height,
-                                          display->wl_output);
+
+       // this will set-up the x and y position to the same app as this one.
+       // Note, that in this example, the surface area is the same as the
+       // output dimensions streamed by the remote compositor
+       client->SetAppFloat(role, g_window_pos_x, g_window_pos_y);
 
        // we use the role to set a correspondence between the top level
        // surface and our application, with the previous call letting the
        // compositor know that we're one and the same
-       window = create_window(display, g_window_width,
-                              g_window_height, role.c_str());
+       //
+       window = create_window(display, g_window_width, g_window_height, role.c_str());
 
        if (!window)
                return -1;
@@ -929,5 +947,7 @@ int main(int argc, char *argv[])
 
        gst_element_set_state(pipeline, GST_STATE_NULL);
        gst_object_unref(pipeline);
+       delete client;
+
        return ret;
 }
diff --git a/app/meson.build b/app/meson.build
new file mode 100644 (file)
index 0000000..d0ea031
--- /dev/null
@@ -0,0 +1,118 @@
+pkgconfig = import('pkgconfig')
+cpp = meson.get_compiler('cpp')
+
+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@/',
+                         '--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,
+]
+
+gstreamer_deps = [
+  'gstreamer-1.0', 'gstreamer-allocators-1.0',
+  'gstreamer-app-1.0', 'gstreamer-video-1.0',
+  'gstreamer-plugins-bad-1.0',
+  'gstreamer-plugins-base-1.0',
+  'gobject-2.0', 'glib-2.0',
+]
+
+deps_remoting = []
+foreach depname : gstreamer_deps
+  dep = dependency(depname, required: false)
+  if not dep.found()
+    error('requires @0@ which was not found. '.format(depname))
+  endif
+  deps_remoting += dep
+endforeach
+
+
+
+dep_wayland_client = dependency('wayland-client', version: '>= 1.17.0')
+dep_scanner = dependency('wayland-scanner', native: true)
+prog_scanner = find_program(dep_scanner.get_variable(pkgconfig: 'wayland_scanner'))
+dep_wp = dependency('wayland-protocols', version: '>= 1.18')
+dir_wp_base = dep_wp.get_variable(pkgconfig: 'pkgdatadir')
+
+cluster_receiver_dep = [
+    grpc_deps, deps_remoting, dep_wayland_client, cpp.find_library('gstwayland-1.0'),
+]
+
+protocols = [
+  { 'name': 'xdg-shell', 'source': 'wp-stable' },
+]
+
+foreach proto: protocols
+    proto_name = proto['name']
+    if proto['source'] == 'wp-stable'
+        base_file = proto_name
+        xml_path = join_paths(dir_wp_base, 'stable', proto_name, '@0@.xml'.format(base_file))
+    else
+        base_file = '@0@-unstable-@1@'.format(proto_name, proto['version'])
+        xml_path = join_paths(dir_wp_base, 'unstable', proto_name, '@0@.xml'.format(base_file))
+    endif
+
+    foreach output_type: [ 'client-header', 'server-header', 'private-code' ]
+        if output_type == 'client-header'
+            output_file = '@0@-client-protocol.h'.format(base_file)
+        elif output_type == 'server-header'
+            output_file = '@0@-server-protocol.h'.format(base_file)
+        else
+            output_file = '@0@-protocol.c'.format(base_file)
+            if dep_scanner.version().version_compare('< 1.14.91')
+                output_type = 'code'
+            endif
+        endif
+
+        var_name = output_file.underscorify()
+        target = custom_target(
+            '@0@ @1@'.format(base_file, output_type),
+            command: [ prog_scanner, output_type, '@INPUT@', '@OUTPUT@' ],
+            input: xml_path,
+            output: output_file,
+        )
+
+        set_variable(var_name, target)
+    endforeach
+endforeach
+
+cluster_receiver_src_headers = [
+  'AglShellGrpcClient.h',
+  xdg_shell_client_protocol_h,
+]
+
+cluster_receiver_src = [
+  'AglShellGrpcClient.cpp',
+  'main.cpp',
+  xdg_shell_protocol_c,
+  generated_grpc_sources,
+  generated_protoc_sources,
+]
+
+prefix_path = get_option('prefix')
+binplugin_dir = join_paths(prefix_path, get_option('bindir'))
+
+executable('xdg-cluster-receiver',
+            sources: [ cluster_receiver_src, cluster_receiver_src_headers ],
+            dependencies : cluster_receiver_dep,
+            install_rpath: binplugin_dir,
+            install: true)
diff --git a/app/protocol/agl-shell-desktop.xml b/app/protocol/agl-shell-desktop.xml
deleted file mode 100644 (file)
index e8ae153..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<protocol name="agl_shell_desktop">
-  <copyright>
-    Copyright © 2020 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.
-  </copyright>
-  <interface name="agl_shell_desktop" version="1">
-    <description summary="Private extension to allow applications activate other apps">
-      This extension can be used by regular application to instruct to compositor
-      to activate or switch to other running (regular) applications. The client
-      is responsbile for filtering their own app_id when receiving application id.
-
-      The compositor will allow clients to bind to this interface only if the
-      policy engine allows it.
-    </description>
-
-    <enum name="app_role">
-      <entry name="popup" value="0"/>
-      <entry name="fullscreen" value="1"/>
-      <entry name="split_vertical" value="2"/>
-      <entry name="split_horizontal" value="3"/>
-      <entry name="remote" value="4"/>
-    </enum>
-
-    <enum name="app_state">
-      <entry name="activated" value="0"/>
-      <entry name="deactivated" value="1"/>
-    </enum>
-
-    <event name="application">
-      <description summary="advertise application id">
-        The compositor may choose to advertise one or more application ids which
-        can be used to activate/switch to.
-
-        When this global is bound, the compositor will send all application ids
-        available for activation, but may send additional application id at any
-        time (when they've been mapped in the compositor).
-      </description>
-      <arg name="app_id" type="string"/>
-    </event>
-
-    <request name="activate_app">
-      <description summary="make client current window">
-        Ask the compositor to make a toplevel to become the current/focused
-        window for window management purposes.
-
-        See xdg_toplevel.set_app_id from the xdg-shell protocol for a
-        description of app_id.
-      </description>
-      <arg name="app_id" type="string"/>
-      <arg name="app_data" type="string" allow-null="true"/>
-      <arg name="output" type="object" interface="wl_output"/>
-    </request>
-
-    <request name="set_app_property">
-      <description summary="set properties for a client identified by app_id">
-        Ask the compositor to make a top-level window obey the 'app_role' enum
-        and, depending on that role, to use some of the arguments as initial
-        values to take into account.
-
-        Note that x, y, bx, by, width and height would only make sense for the
-        pop-up role, with the output argument being applicable to all the roles.
-        The width and height values define the maximum area which the
-        top-level window should be placed into. Note this doesn't correspond to
-        top-level surface size, but to a bounding box which will be used to
-        clip the surface to, in case the surface area extends that of this
-        bounding box. Both of these values need to be larger than 0 (zero) to be
-        taken into account by the compositor. Any negative values for the width
-        and height will be discarded.
-
-        The x and y values will serve as the (initial) position values.
-        The bx and by values are the top-left x and y value of the bounding box.
-        Any clipping happening to the bounding box will not affect the surface
-        size or the position of the underlying surface backing the top-level
-        window. The bx and by values, like the positional values, could be
-        both set to zero, or even negative values. The compositor will pass
-        those on without any further validation.
-
-        The initial position values and the bounding rectangle will still be
-        in effect on a subsequent activation request of the 'app_id', assuming
-        it was previously de-activated at some point in time.
-
-        See xdg_toplevel.set_app_id from the xdg-shell protocol for a
-        description of app_id.
-      </description>
-      <arg name="app_id" type="string"/>
-      <arg name="role" type="uint" enum="app_role"/>
-      <arg name="x" type="int"/>
-      <arg name="y" type="int"/>
-      <arg name="bx" type="int"/>
-      <arg name="by" type="int"/>
-      <arg name="width" type="int"/>
-      <arg name="height" type="int"/>
-      <arg name="output" type="object" interface="wl_output"/>
-    </request>
-
-    <request name="deactivate_app">
-      <description summary="de-activate/hide window identified by app_id">
-        Ask the compositor to hide the toplevel window for window
-        management purposes. Depending on the window role, this request
-        will either display the previously active window (or the background
-        in case there's no previously activate surface) or temporarly (or
-        until a 'activate_app' is called upon) hide the surface. All
-        the surfaces are identifiable by using the app_id, and no actions are
-        taken in case the app_id is not/was not present.
-
-        See xdg_toplevel.set_app_id from the xdg-shell protocol for a
-        description of app_id.
-      </description>
-      <arg name="app_id" type="string"/>
-    </request>
-
-    <event name="state_app">
-      <description summary="event sent when application has suffered state modification">
-        Notifies application(s) when other application have suffered state modifications.
-      </description>
-      <arg name="app_id" type="string"/>
-      <arg name="app_data" type="string" allow-null="true"/>
-      <arg name="state" type="uint" enum="app_state"/>
-      <arg name="role" type="uint" enum="app_role"/>
-    </event>
-
-  </interface>
-</protocol>
similarity index 57%
rename from CMakeLists.txt
rename to meson.build
index 4685915..0d36cd9 100644 (file)
@@ -1,22 +1,30 @@
-###########################################################################
-# Copyright 2015,2016,2017 IoT.bzh
-# Copyright 2022 Konsulko Group
 #
-# Author: romain Forlot <romain.forlot@iot.bzh>
+# Copyright 2024 Collabora, Ltd.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
 #
-#     http://www.apache.org/licenses/LICENSE-2.0
+#      http://www.apache.org/licenses/LICENSE-2.0
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-###########################################################################
+#
 
-CMAKE_MINIMUM_REQUIRED(VERSION 3.3)
+project('cluster-receiver',
+  'c', 'cpp',
+  version: '0.0.1',
+  default_options: [
+    'warning_level=3',
+    'werror=true',
+    'c_std=c2x',
+    'cpp_std=c++20'
+  ],
+  meson_version: '>= 1.0',
+  license: 'MIT/Expat',
+)
 
-add_subdirectory(app)
+subdir('app')