Init camera-gstreamer 15/26115/2 10.0.3 11.0.3 11.0.4 11.91.0 11.92.0 12.90.0 12.90.1 jellyfish/10.0.3 jellyfish_10.0.3 koi/11.0.3 koi/11.0.4 koi_11.0.3 koi_11.0.4 lamprey/11.91.0 lamprey/11.92.0 lamprey_11.91.0 lamprey_11.92.0 marlin/12.90.0 marlin/12.90.1 marlin_12.90.0 marlin_12.90.1
authorMarius Vlad <marius.vlad@collabora.com>
Tue, 2 Mar 2021 16:30:32 +0000 (18:30 +0200)
committerMarius Vlad <marius.vlad@collabora.com>
Wed, 3 Mar 2021 11:29:39 +0000 (13:29 +0200)
Bug-AGL: SPEC-3629

Signed-off-by: Marius Vlad <marius.vlad@collabora.com>
Change-Id: Ib1a221f03dfac3114bbd3477dc488dd90c86ba35

14 files changed:
.gitignore [new file with mode: 0644]
.gitreview [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
LICENSE [new file with mode: 0644]
README.md [new file with mode: 0644]
app/CMakeLists.txt [new file with mode: 0644]
app/main.cpp [new file with mode: 0644]
app/protocol/agl-shell-desktop.xml [new file with mode: 0644]
app/utils.cpp [new file with mode: 0644]
app/utils.h [new file with mode: 0644]
autobuild/agl/autobuild [new file with mode: 0755]
autobuild/linux/autobuild [new file with mode: 0755]
conf.d/cmake/config.cmake [new file with mode: 0644]
conf.d/wgt/config.xml.in [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..5975d97
--- /dev/null
@@ -0,0 +1,2 @@
+*~
+build/
diff --git a/.gitreview b/.gitreview
new file mode 100644 (file)
index 0000000..e839a3f
--- /dev/null
@@ -0,0 +1,5 @@
+[gerrit]
+host=gerrit.automotivelinux.org
+port=29418
+project=apps/camera-gstreamer
+defaultbranch=master
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..50cee76
--- /dev/null
@@ -0,0 +1,19 @@
+# Copyright 2021 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
+#
+# 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(camera-gstreamer)
+include(${CMAKE_CURRENT_SOURCE_DIR}/conf.d/cmake/config.cmake)
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..31c692a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,54 @@
+Apache License
+
+Version 2.0, January 2004
+
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
+
+"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
+
+    You must give any other recipients of the Work or Derivative Works a copy of this License; and
+    You must cause any modified files to carry prominent notices stating that You changed the files; and
+    You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
+    If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
+
+    You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..a7f64b7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,5 @@
+camera-gstreamer
+----------------
+
+Assumes that /dev/video0 is present and is set as a capture device.  Use
+DEFAULT_V4L2_DEVICE environmental variable to change it.
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
new file mode 100644 (file)
index 0000000..02322d4
--- /dev/null
@@ -0,0 +1,124 @@
+###########################################################################
+# Copyright 2018 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.
+###########################################################################
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+find_package(PkgConfig REQUIRED)
+find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner)
+
+PROJECT_TARGET_ADD(camera-gstreamer)
+
+add_custom_command(
+       OUTPUT  agl-shell-desktop-client-protocol.h
+       COMMAND ${WAYLAND_SCANNER_EXECUTABLE} client-header
+       < ${CMAKE_SOURCE_DIR}/app/protocol/agl-shell-desktop.xml
+       > ${CMAKE_SOURCE_DIR}/app/agl-shell-desktop-client-protocol.h
+       DEPENDS ${CMAKE_SOURCE_DIR}/app/protocol/agl-shell-desktop.xml
+)
+
+
+add_custom_command(
+       OUTPUT  ${CMAKE_BINARY_DIR}/app/agl-shell-desktop-client-protocol.h
+       COMMAND ${WAYLAND_SCANNER_EXECUTABLE} client-header
+       < ${CMAKE_SOURCE_DIR}/app/protocol/agl-shell-desktop.xml
+       > ${CMAKE_SOURCE_DIR}/app/agl-shell-desktop-client-protocol.h
+       DEPENDS ${CMAKE_SOURCE_DIR}/app/protocol/agl-shell-desktop.xml
+)
+
+add_custom_command(
+       OUTPUT  agl-shell-desktop-protocol.c
+       COMMAND ${WAYLAND_SCANNER_EXECUTABLE} code
+       < ${CMAKE_SOURCE_DIR}/app/protocol/agl-shell-desktop.xml
+       > ${CMAKE_BINARY_DIR}/app/agl-shell-desktop-protocol.c
+       DEPENDS ${CMAKE_SOURCE_DIR}/app/protocol/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(${TARGET_NAME}
+       main.cpp
+       utils.h
+       utils.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}"
+)
+
+set_target_properties(${TARGET_NAME} PROPERTIES
+       LABELS "EXECUTABLE"
+       PREFIX ""
+       COMPILE_FLAGS "${EXTRAS_CFLAGS} -DFOR_AFB_BINDING"
+       LINK_FLAGS "${BINDINGS_LINK_FLAG}"
+       LINK_LIBRARIES "${EXTRAS_LIBRARIES}"
+       OUTPUT_NAME "${TARGET_NAME}"
+)
+
+target_link_libraries(${TARGET_NAME}
+       ${GSTREAMER_LIBRARIES}
+       "${GSTREAMER_PLUGINS_BASE_LIBRARIES}"
+       "${GSTREAMER_PLUGINS_BAD_LIBRARIES}"
+       "${GSTREAMER_VIDEO_LIBRARIES}"
+       ${WAYLAND_CLIENT_LIBRARIES}
+       -lgstwayland-1.0
+)
diff --git a/app/main.cpp b/app/main.cpp
new file mode 100644 (file)
index 0000000..8634bbc
--- /dev/null
@@ -0,0 +1,739 @@
+#define GST_USE_UNSTABLE_API
+#define HAVE_MEMFD_CREATE
+
+#include <string>
+#include <iostream>
+#include <cstring>
+#include <cstdio>
+#include <cstdlib>
+
+#include <signal.h>
+#include <wayland-client.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "xdg-shell-client-protocol.h"
+#include "agl-shell-desktop-client-protocol.h"
+#include "utils.h"
+
+#include <gst/gst.h>
+
+#include <gst/video/videooverlay.h>
+#include <gst/wayland/wayland.h>
+
+#define DEFAULT_VIDEO_DEVICE   "/dev/video0"
+
+// these only applies if the window is a dialog/pop-up one
+// by default the compositor make the window maximized
+#define WINDOW_WIDTH_SIZE      640
+#define WINDOW_HEIGHT_SIZE     720
+
+#define WINDOW_WIDTH_POS_X     640
+#define WINDOW_WIDTH_POS_Y     180
+
+// C++ requires a cast and we in wayland we do the cast implictly
+#define WL_ARRAY_FOR_EACH(pos, array, type) \
+       for (pos = (type)(array)->data; \
+            (const char *) pos < ((const char *) (array)->data + (array)->size); \
+            (pos)++)
+
+struct display {
+       struct wl_display *wl_display;
+       struct wl_registry *wl_registry;
+       struct wl_compositor *wl_compositor;
+       struct wl_output *wl_output;
+       struct wl_shm *shm;
+
+       struct {
+               int width;
+               int height;
+       } output_data;
+
+       struct xdg_wm_base *wm_base;
+       struct agl_shell_desktop *agl_shell_desktop;
+
+       int has_xrgb;
+};
+
+struct buffer {
+       struct wl_buffer *buffer;
+       void *shm_data;
+       int busy;
+};
+
+struct window {
+       struct display *display;
+
+       int x, y;
+       int width, height;
+
+       struct wl_surface *surface;
+
+       struct xdg_surface *xdg_surface;
+       struct xdg_toplevel *xdg_toplevel;
+       bool wait_for_configure;
+
+       int fullscreen, maximized;
+
+       struct buffer buffers[2];
+       struct wl_callback *callback;
+};
+
+
+struct receiver_data {
+       struct window *window;
+
+       GstElement *pipeline;
+       GstWaylandVideo *wl_video;
+       GstVideoOverlay *overlay;
+};
+
+static int running = 1;
+
+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)
+{
+       memset(image, 0x00, width * height * 4);
+}
+
+static void
+buffer_release(void *data, struct wl_buffer *buffer)
+{
+       struct buffer *mybuf = static_cast<struct buffer *>(data);
+       mybuf->busy = 0;
+}
+
+static const struct wl_buffer_listener buffer_listener = {
+       buffer_release
+};
+
+
+static int
+create_shm_buffer(struct display *display, struct buffer *buffer,
+                 int width, int height, uint32_t format)
+{
+       struct wl_shm_pool *pool;
+       int fd, size, stride;
+       void *data;
+
+       stride = width * 4;
+       size = stride * height;
+
+       fd = os_create_anonymous_file(size);
+       if (fd < 0) {
+               fprintf(stderr, "creating a buffer file for %d B failed: %s\n",
+                               size, strerror(errno));
+               return -1;
+       }
+
+       data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+       if (data == MAP_FAILED) {
+               fprintf(stderr, "mmap failed: %s\n", strerror(errno));
+               close(fd);
+               return -1;
+       }
+
+       pool = wl_shm_create_pool(display->shm, fd, size);
+       buffer->buffer = wl_shm_pool_create_buffer(pool, 0, width,
+                                                  height, stride, format);
+       wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer);
+       wl_shm_pool_destroy(pool);
+       close(fd);
+
+       buffer->shm_data = data;
+       fprintf(stdout, "Created shm buffer with width %d, height %d\n", width, height);
+       return 0;
+}
+
+static struct buffer *
+get_next_buffer(struct window *window)
+{
+       struct buffer *buffer = NULL;
+       int ret = 0;
+
+       /* we need to create new buffers for the resized value so discard
+        * the 'old' one and force creation of the buffer with the newer
+        * dimensions */
+       if (window->wait_for_configure && window->maximized) {
+               if (!window->buffers[0].busy) {
+                       wl_buffer_destroy(window->buffers[0].buffer);
+                       window->buffers[0].buffer = NULL;
+                       window->wait_for_configure = false;
+               }
+       }
+
+       if (!window->buffers[0].busy) {
+               buffer = &window->buffers[0];
+       } else if (!window->buffers[1].busy) {
+               buffer = &window->buffers[1];
+       } else {
+               return NULL;
+       }
+
+       if (!buffer->buffer) {
+               ret = create_shm_buffer(window->display, buffer, window->width,
+                                       window->height, WL_SHM_FORMAT_XRGB8888);
+
+               if (ret < 0)
+                       return NULL;
+
+               /* paint the padding */
+               memset(buffer->shm_data, 0x00, window->width * window->height * 4);
+       }
+
+       return buffer;
+}
+
+
+static const struct wl_callback_listener frame_listener = {
+       redraw
+};
+
+static void
+redraw(void *data, struct wl_callback *callback, uint32_t time)
+{
+        struct window *window = static_cast<struct window *>(data);
+        struct buffer *buffer;
+
+        buffer = get_next_buffer(window);
+        if (!buffer) {
+                fprintf(stderr,
+                        !callback ? "Failed to create the first buffer.\n" :
+                        "Both buffers busy at redraw(). Server bug?\n");
+                abort();
+        }
+
+       // do the actual painting
+       paint_pixels(buffer->shm_data, 0x0, window->width, window->height, time);
+
+        wl_surface_attach(window->surface, buffer->buffer, 0, 0);
+        wl_surface_damage(window->surface, 0, 0, window->width, window->height);
+
+        if (callback)
+                wl_callback_destroy(callback);
+
+        window->callback = wl_surface_frame(window->surface);
+        wl_callback_add_listener(window->callback, &frame_listener, window);
+        wl_surface_commit(window->surface);
+
+        buffer->busy = 1;
+}
+
+static void
+shm_format(void *data, struct wl_shm *wl_shm, uint32_t format)
+{
+       struct display *d = static_cast<struct display *>(data);
+
+       if (format == WL_SHM_FORMAT_XRGB8888)
+               d->has_xrgb = true;
+}
+
+static const struct wl_shm_listener shm_listener = {
+       shm_format
+};
+
+static void
+xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial)
+{
+        xdg_wm_base_pong(shell, serial);
+}
+
+static const struct xdg_wm_base_listener xdg_wm_base_listener = {
+        xdg_wm_base_ping,
+};
+
+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)
+{
+       struct display *d = static_cast<struct display *>(data);
+
+       if (wl_output == d->wl_output && (flags & WL_OUTPUT_MODE_CURRENT)) {
+               d->output_data.width = width;
+               d->output_data.height = height;
+
+               fprintf(stdout, "Found output with width %d and height %d\n",
+                               d->output_data.width, d->output_data.height);
+       }
+}
+
+static void
+display_handle_scale(void *data, struct wl_output *wl_output, int scale)
+{
+       (void) data;
+       (void) wl_output;
+       (void) scale;
+}
+
+static void
+display_handle_done(void *data, struct wl_output *wl_output)
+{
+       (void) data;
+       (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)
+{
+       (void) data;
+       (void) agl_shell_desktop;
+       (void) app_id;
+}
+
+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)
+{
+        (void) data;
+        (void) app_data;
+        (void) agl_shell_desktop;
+        (void) app_id;
+        (void) app_state;
+        (void) app_role;
+}
+
+static const struct agl_shell_desktop_listener agl_shell_desktop_listener = {
+        application_id,
+        application_id_state,
+};
+
+static void
+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);
+
+       if (strcmp(interface, "wl_compositor") == 0) {
+               d->wl_compositor =
+                       static_cast<struct wl_compositor *>(wl_registry_bind(registry, id,
+                                                   &wl_compositor_interface, 1));
+       } else if (strcmp(interface, "xdg_wm_base") == 0) {
+               d->wm_base = static_cast<struct xdg_wm_base *>(wl_registry_bind(registry,
+                               id, &xdg_wm_base_interface, 1));
+               xdg_wm_base_add_listener(d->wm_base, &xdg_wm_base_listener, d);
+       } else if (strcmp(interface, "wl_shm") == 0) {
+               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));
+               wl_output_add_listener(d->wl_output, &output_listener, d);
+       }
+}
+
+static void
+registry_handle_global_remove(void *data, struct wl_registry *reg, uint32_t id)
+{
+       (void) data;
+       (void) reg;
+       (void) id;
+}
+
+static const struct wl_registry_listener registry_listener = {
+       registry_handle_global,
+       registry_handle_global_remove,
+};
+
+
+static void
+error_cb(GstBus *bus, GstMessage *msg, gpointer user_data)
+{
+       struct receiver_data *d =
+               static_cast<struct receiver_data *>(user_data);
+
+       gchar *debug = NULL;
+       GError *err = NULL;
+
+       gst_message_parse_error(msg, &err, &debug);
+
+       g_print("Error: %s\n", err->message);
+       g_error_free(err);
+
+       if (debug) {
+               g_print("Debug details: %s\n", debug);
+               g_free(debug);
+       }
+
+       gst_element_set_state(d->pipeline, GST_STATE_NULL);
+}
+
+static GstBusSyncReply
+bus_sync_handler(GstBus *bus, GstMessage *message, gpointer user_data)
+{
+       struct receiver_data *d =
+               static_cast<struct receiver_data *>(user_data);
+
+       if (gst_is_wayland_display_handle_need_context_message(message)) {
+               GstContext *context;
+               struct wl_display *display_handle = d->window->display->wl_display;
+
+               context = gst_wayland_display_handle_context_new(display_handle);
+               d->wl_video = GST_WAYLAND_VIDEO(GST_MESSAGE_SRC(message));
+               gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(message)), context);
+
+               goto drop;
+       } else if (gst_is_video_overlay_prepare_window_handle_message(message)) {
+               struct wl_surface *window_handle = d->window->surface;
+
+               /* GST_MESSAGE_SRC(message) will be the overlay object that we
+                * have to use. This may be waylandsink, but it may also be
+                * playbin. In the latter case, we must make sure to use
+                * playbin instead of waylandsink, because playbin resets the
+                * window handle and render_rectangle after restarting playback
+                * and the actual window size is lost */
+               d->overlay = GST_VIDEO_OVERLAY(GST_MESSAGE_SRC(message));
+
+               g_print("setting window handle and size (%d x %d) w %d, h %d\n",
+                               d->window->x, d->window->y,
+                               d->window->width, d->window->height);
+
+               gst_video_overlay_set_window_handle(d->overlay, (guintptr) window_handle);
+               gst_video_overlay_set_render_rectangle(d->overlay,
+                                                      d->window->x, d->window->y,
+                                                      d->window->width, d->window->height);
+
+               goto drop;
+       }
+
+       return GST_BUS_PASS;
+
+drop:
+       gst_message_unref(message);
+       return GST_BUS_DROP;
+}
+
+static void
+handle_xdg_surface_configure(void *data, struct xdg_surface *surface, uint32_t serial)
+{
+       struct window *window = static_cast<struct window *>(data);
+
+       xdg_surface_ack_configure(surface, serial);
+
+       if (window->wait_for_configure) {
+               redraw(window, NULL, 0);
+       }
+}
+
+static const struct xdg_surface_listener xdg_surface_listener = {
+       handle_xdg_surface_configure,
+};
+
+static void
+handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
+                             int32_t width, int32_t height,
+                             struct wl_array *states)
+{
+       struct window *window = static_cast<struct window *>(data);
+       uint32_t *p;
+
+       window->fullscreen = 0;
+       window->maximized = 0;
+
+       // use our own macro as C++ can't typecast from (void *) directly
+       WL_ARRAY_FOR_EACH(p, states, uint32_t *) {
+               uint32_t state = *p; 
+               switch (state) {
+               case XDG_TOPLEVEL_STATE_FULLSCREEN:
+                       window->fullscreen = 1;
+                       break;
+               case XDG_TOPLEVEL_STATE_MAXIMIZED:
+                       window->maximized = 1;
+                       break;
+               }
+       }
+
+       if (width > 0 && height > 0) {
+               if (!window->fullscreen && !window->maximized) {
+                       window->width = width;
+                       window->height = height;
+               }
+               window->width = width;
+               window->height = height;
+       } else if (!window->fullscreen && !window->maximized) {
+               if (width == 0)
+                       window->width = WINDOW_WIDTH_SIZE;
+               else
+                       window->width = width;
+
+               if (height == 0)
+                       window->height = WINDOW_HEIGHT_SIZE;
+               else
+                       window->height = height;
+       }
+
+       /* if we've been resized set wait_for_configure to adjust the fb size 
+        * in the frame callback handler, which will also clear this up */
+       if ((window->width > 0 && window->width != WINDOW_WIDTH_SIZE) &&
+           (window->height > 0 && window->height != WINDOW_HEIGHT_SIZE)) {
+               window->wait_for_configure = true;
+       }
+}
+
+static void
+handle_xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel)
+{
+       running = 0;
+}
+
+static const struct xdg_toplevel_listener xdg_toplevel_listener = {
+       handle_xdg_toplevel_configure,
+       handle_xdg_toplevel_close,
+};
+
+static struct window *
+create_window(struct display *display, int width, int height, const char *app_id)
+{
+       struct window *window;
+
+       assert(display->wm_base != NULL);
+
+       window = static_cast<struct window *>(calloc(1, sizeof(*window)));
+       if (!window)
+               return NULL;
+
+       window->callback = NULL;
+       window->display = display;
+       window->width = width;
+       window->height = height;
+       window->surface = wl_compositor_create_surface(display->wl_compositor);
+
+       if (display->wm_base) {
+               window->xdg_surface =
+                       xdg_wm_base_get_xdg_surface(display->wm_base, window->surface);
+               assert(window->xdg_surface);
+
+               xdg_surface_add_listener(window->xdg_surface,
+                                        &xdg_surface_listener, window);
+               window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface);
+               assert(window->xdg_toplevel);
+
+               xdg_toplevel_add_listener(window->xdg_toplevel,
+                                         &xdg_toplevel_listener, window);
+
+               xdg_toplevel_set_app_id(window->xdg_toplevel, app_id);
+
+               wl_surface_commit(window->surface);
+               window->wait_for_configure = true;
+       }
+
+       return window;
+}
+
+
+static void
+destroy_window(struct window *window)
+{
+       if (window->callback)
+               wl_callback_destroy(window->callback);
+
+       if (window->buffers[0].buffer)
+               wl_buffer_destroy(window->buffers[0].buffer);
+       if (window->buffers[1].buffer)
+               wl_buffer_destroy(window->buffers[1].buffer);
+
+       if (window->xdg_toplevel)
+               xdg_toplevel_destroy(window->xdg_toplevel);
+
+       if (window->xdg_surface)
+               xdg_surface_destroy(window->xdg_surface);
+
+       wl_surface_destroy(window->surface);
+       free(window);
+}
+
+static void
+signal_int(int sig, siginfo_t *si, void *_unused)
+{
+       running = 0;
+}
+
+static struct display *
+create_display(int argc, char *argv[])
+{
+       struct display *display;
+
+       display = static_cast<struct display *>(calloc(1, sizeof(*display)));
+       if (display == NULL) {
+               fprintf(stderr, "out of memory\n");
+               exit(1);
+       }
+       display->wl_display = wl_display_connect(NULL);
+       assert(display->wl_display);
+
+       display->has_xrgb = false;
+       display->wl_registry = wl_display_get_registry(display->wl_display);
+
+       wl_registry_add_listener(display->wl_registry, &registry_listener, display);
+       wl_display_roundtrip(display->wl_display);
+
+       if (display->shm == NULL) {
+               fprintf(stderr, "No wl_shm global\n");
+               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) {
+               fprintf(stderr, "WL_SHM_FORMAT_XRGB32 not available\n");
+               return NULL;
+       }
+
+       return display;
+}
+
+static void
+destroy_display(struct display *display)
+{
+       if (display->shm)
+               wl_shm_destroy(display->shm);
+
+       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);
+
+       wl_registry_destroy(display->wl_registry);
+       wl_display_flush(display->wl_display);
+       wl_display_disconnect(display->wl_display);
+       free(display);
+}
+
+int main(int argc, char *argv[])
+{
+       int ret = 0;
+       struct sigaction sa;
+       struct receiver_data receiver_data = {};
+       struct display *display;
+       struct window *window;
+       char pipeline_str[1024];
+       GError *error = NULL;
+       const char *app_id = "camera-gstreamer";
+
+       sa.sa_sigaction = signal_int;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = SA_RESETHAND | SA_SIGINFO;
+       sigaction(SIGINT, &sa, NULL);
+
+       int gargc = 2;
+       char **gargv = static_cast<char **>(calloc(2, sizeof(char *)));
+
+       gargv[0] = strdup(argv[0]);
+       gargv[1] = strdup("--gst-debug-level=2");
+
+       memset(pipeline_str, 0, sizeof(pipeline_str));
+       snprintf(pipeline_str, sizeof(pipeline_str), "v4l2src device=%s ! video/x-raw,width=%d,height=%d ! waylandsink", 
+               DEFAULT_VIDEO_DEVICE, WINDOW_WIDTH_SIZE, WINDOW_HEIGHT_SIZE);
+       gst_init(&gargc, &gargv);
+
+       fprintf(stdout, "Using pipeline: %s\n", pipeline_str);
+
+       GstElement *pipeline = gst_parse_launch(pipeline_str, &error);
+       if (error || !pipeline) {
+               fprintf(stderr, "gstreamer pipeline construction failed!\n");
+               free(argv);
+               return EXIT_FAILURE;
+       }
+
+       receiver_data.pipeline = pipeline;
+
+       display = create_display(argc, argv);
+       if (!display)
+               return -1;
+
+       // if you'd want to place the video in a pop-up/dialog type of window:
+       // agl_shell_desktop_set_app_property(display->agl_shell_desktop, app_id, 
+       //                                 AGL_SHELL_DESKTOP_APP_ROLE_POPUP,
+       //                                 WINDOW_WIDTH_POS_X, WINDOW_WIDTH_POS_Y,
+       //                                 0, 0, WINDOW_WIDTH_SIZE, WINDOW_HEIGHT_SIZE,
+       //                                 display->wl_output);
+
+       // 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, WINDOW_WIDTH_SIZE, WINDOW_HEIGHT_SIZE, app_id); 
+
+       if (!window) {
+               free(argv);
+               return EXIT_FAILURE;
+       }
+
+       window->display = display;
+       receiver_data.window = window;
+
+       /* Initialise damage to full surface, so the padding gets painted */
+       wl_surface_damage(window->surface, 0, 0,
+                         window->width, window->height);
+
+       if (!window->wait_for_configure) {
+               redraw(window, NULL, 0);
+       }
+
+       GstBus *bus = gst_element_get_bus(pipeline);
+       gst_bus_add_signal_watch(bus);
+
+       g_signal_connect(bus, "message::error", G_CALLBACK(error_cb), &receiver_data);
+       gst_bus_set_sync_handler(bus, bus_sync_handler, &receiver_data, NULL);
+       gst_object_unref(bus);
+
+       gst_element_set_state(pipeline, GST_STATE_PLAYING);
+       fprintf(stdout, "gstreamer pipeline running\n");
+
+       // run the application
+       while (running && ret != -1)
+               ret = wl_display_dispatch(display->wl_display);
+
+       destroy_window(window);
+       destroy_display(display);
+       free(argv);
+
+       gst_element_set_state(pipeline, GST_STATE_NULL);
+       gst_object_unref(pipeline);
+       return ret;
+}
diff --git a/app/protocol/agl-shell-desktop.xml b/app/protocol/agl-shell-desktop.xml
new file mode 100644 (file)
index 0000000..e8ae153
--- /dev/null
@@ -0,0 +1,142 @@
+<?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>
diff --git a/app/utils.cpp b/app/utils.cpp
new file mode 100644 (file)
index 0000000..f1c3eae
--- /dev/null
@@ -0,0 +1,150 @@
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <cstdlib>
+#include <cstring>
+#include <cerrno>
+
+#include "utils.h"
+
+static int
+os_fd_set_cloexec(int fd)
+{
+       long flags;
+
+       if (fd == -1)
+               return -1;
+
+       flags = fcntl(fd, F_GETFD);
+       if (flags == -1)
+               return -1;
+
+       if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
+               return -1;
+
+       return 0;
+}
+
+static int
+set_cloexec_or_close(int fd)
+{
+       if (os_fd_set_cloexec(fd) != 0) {
+               close(fd);
+               return -1;
+       }
+       return fd;
+}
+
+static int
+create_tmpfile_cloexec(char *tmpname)
+{
+       int fd;
+
+#ifdef HAVE_MKOSTEMP
+       fd = mkostemp(tmpname, O_CLOEXEC);
+       if (fd >= 0)
+               unlink(tmpname);
+#else
+       fd = mkstemp(tmpname);
+       if (fd >= 0) {
+               fd = set_cloexec_or_close(fd);
+               unlink(tmpname);
+       }
+#endif
+
+       return fd;
+}
+
+/*
+ * Create a new, unique, anonymous file of the given size, and
+ * return the file descriptor for it. The file descriptor is set
+ * CLOEXEC. The file is immediately suitable for mmap()'ing
+ * the given size at offset zero.
+ *
+ * The file should not have a permanent backing store like a disk,
+ * but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
+ *
+ * The file name is deleted from the file system.
+ *
+ * The file is suitable for buffer sharing between processes by
+ * transmitting the file descriptor over Unix sockets using the
+ * SCM_RIGHTS methods.
+ *
+ * If the C library implements posix_fallocate(), it is used to
+ * guarantee that disk space is available for the file at the
+ * given size. If disk space is insufficient, errno is set to ENOSPC.
+ * If posix_fallocate() is not supported, program may receive
+ * SIGBUS on accessing mmap()'ed file contents instead.
+ *
+ * If the C library implements memfd_create(), it is used to create the
+ * file purely in memory, without any backing file name on the file
+ * system, and then sealing off the possibility of shrinking it.  This
+ * can then be checked before accessing mmap()'ed file contents, to
+ * make sure SIGBUS can't happen.  It also avoids requiring
+ * XDG_RUNTIME_DIR.
+ */
+int
+os_create_anonymous_file(off_t size)
+{
+       static const char weston_template[] = "/weston-shared-XXXXXX";
+       const char *path;
+       char *name;
+       int fd;
+       int ret;
+
+#ifdef HAVE_MEMFD_CREATE
+       fd = memfd_create("weston-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING);
+       if (fd >= 0) {
+               /* We can add this seal before calling posix_fallocate(), as
+                * the file is currently zero-sized anyway.
+                *
+                * There is also no need to check for the return value, we
+                * couldn't do anything with it anyway.
+                */
+               fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK);
+       } else
+#endif
+       {
+               path = getenv("XDG_RUNTIME_DIR");
+               if (!path) {
+                       errno = ENOENT;
+                       return -1;
+               }
+
+               name = static_cast<char *>(malloc(strlen(path) + sizeof(weston_template)));
+               if (!name)
+                       return -1;
+
+               strcpy(name, path);
+               strcat(name, weston_template);
+
+               fd = create_tmpfile_cloexec(name);
+
+               free(name);
+
+               if (fd < 0)
+                       return -1;
+       }
+
+#ifdef HAVE_POSIX_FALLOCATE
+       do {
+               ret = posix_fallocate(fd, 0, size);
+       } while (ret == EINTR);
+       if (ret != 0) {
+               close(fd);
+               errno = ret;
+               return -1;
+       }
+#else
+       do {
+               ret = ftruncate(fd, size);
+       } while (ret < 0 && errno == EINTR);
+       if (ret < 0) {
+               close(fd);
+               return -1;
+       }
+#endif
+
+       return fd;
+}
diff --git a/app/utils.h b/app/utils.h
new file mode 100644 (file)
index 0000000..99b2e30
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef __UTILS_H
+#define __UTILS_H
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+int
+os_create_anonymous_file(off_t size);
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif
diff --git a/autobuild/agl/autobuild b/autobuild/agl/autobuild
new file mode 100755 (executable)
index 0000000..16181b8
--- /dev/null
@@ -0,0 +1,128 @@
+#!/usr/bin/make -f
+# Copyright (C) 2015 - 2018 "IoT.bzh"
+# Copyright (C) 2020 Konsulko Group
+# Author "Romain Forlot" <romain.forlot@iot.bzh>
+#
+# 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.
+
+THISFILE  := $(lastword $(MAKEFILE_LIST))
+ROOT_DIR := $(abspath $(dir $(THISFILE))/../..)
+
+# Build directories
+# Note that the debug/test/coverage directories are defined in relation
+# to the release directory (BUILD_DIR), this needs to be kept in mind
+# if over-riding it and building those widget types, the specific widget
+# type variable (e.g. BUILD_DIR_DEBUG) may also need to be specified
+# to yield the desired output hierarchy.
+BUILD_DIR = $(ROOT_DIR)/build
+BUILD_DIR_DEBUG = $(abspath $(BUILD_DIR)/../build-debug)
+BUILD_DIR_TEST = $(abspath $(BUILD_DIR)/../build-test)
+BUILD_DIR_COVERAGE = $(abspath $(BUILD_DIR)/../build-coverage)
+
+# Output directory variable for use in pattern rules.
+# This is intended for internal use only, hence the explicit override
+# definition.
+override OUTPUT_DIR = $(BUILD_DIR)
+
+# Final install directory for widgets
+DEST = $(OUTPUT_DIR)
+
+# Default build type for release/test builds
+BUILD_TYPE = RELEASE
+
+.PHONY: all help update install distclean
+.PHONY: clean clean-release clean-debug clean-test clean-coverage clean-all
+.PHONY: configure configure-release configure-debug configure-test configure-coverage
+.PHONY: build build-release build-debug build-test build-coverage build-all
+.PHONY: package package-release package-debug package-test package-coverage package-all
+
+help:
+       @echo "List of targets available:"
+       @echo ""
+       @echo "- all"
+       @echo "- help"
+       @echo "- clean"
+       @echo "- distclean"
+       @echo "- configure"
+       @echo "- build: compilation, link and prepare files for package into a widget"
+       @echo "- package: output a widget file '*.wgt'"
+       @echo "- install: install in your $(CMAKE_INSTALL_DIR) directory"
+       @echo ""
+       @echo "Usage: ./autobuild/agl/autobuild package DEST=${HOME}/opt"
+       @echo "Don't use your build dir as DEST as wgt file is generated at this location"
+
+all: package-all
+
+# Target specific variable over-rides so static pattern rules can be
+# used for the various type-specific targets.
+
+configure-test build-test package-test clean-test: OUTPUT_DIR = $(BUILD_DIR_TEST)
+
+configure-coverage build-coverage package-coverage clean-coverage: OUTPUT_DIR = $(BUILD_DIR_COVERAGE)
+configure-coverage build-coverage package-coverage: BUILD_TYPE = COVERAGE
+
+configure-debug build-debug package-debug clean-debug: OUTPUT_DIR = $(BUILD_DIR_DEBUG)
+configure-debug build-debug package-debug: BUILD_TYPE = DEBUG
+
+clean-release clean-test clean-debug clean-coverage:
+       @if [ -d $(OUTPUT_DIR) ]; then \
+               $(MAKE) -C $(OUTPUT_DIR) $(CLEAN_ARGS) clean; \
+       else \
+               echo Nothing to clean; \
+       fi
+
+clean: clean-release
+
+clean-all: clean-release clean-test clean-debug clean-coverage
+
+distclean: clean-all
+
+configure-release configure-test configure-debug configure-coverage:
+       @mkdir -p $(OUTPUT_DIR)
+       @if [ ! -f $(OUTPUT_DIR)/Makefile ]; then \
+               (cd $(OUTPUT_DIR) && cmake -S $(ROOT_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) $(CONFIGURE_ARGS)); \
+       fi
+
+configure: configure-release
+
+build-release build-debug build-coverage: build-%: configure-%
+       @cmake --build $(OUTPUT_DIR) $(BUILD_ARGS) --target all
+
+# Kept for consistency, empty to avoid building everything for test widget
+build-test: configure-test
+
+build: build-release
+
+build-all: build-release build-debug build-test build-coverage
+
+package-release package-debug package-coverage: package-%: build-%
+       @cmake --build $(OUTPUT_DIR) $(PACKAGE_ARGS) --target widget
+       @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \
+               mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \
+       fi
+
+package-test: build-test
+       @cmake --build $(OUTPUT_DIR) $(PACKAGE_ARGS) --target test_widget
+       @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \
+               mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \
+       fi
+
+package: package-release
+
+package-all: package-release package-test package-coverage package-debug
+
+update: configure
+       @cmake --build $(BUILD_DIR) --target autobuild
+
+install: build
+       @cmake --build $(BUILD_DIR) $(INSTALL_ARGS) --target install
diff --git a/autobuild/linux/autobuild b/autobuild/linux/autobuild
new file mode 100755 (executable)
index 0000000..16181b8
--- /dev/null
@@ -0,0 +1,128 @@
+#!/usr/bin/make -f
+# Copyright (C) 2015 - 2018 "IoT.bzh"
+# Copyright (C) 2020 Konsulko Group
+# Author "Romain Forlot" <romain.forlot@iot.bzh>
+#
+# 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.
+
+THISFILE  := $(lastword $(MAKEFILE_LIST))
+ROOT_DIR := $(abspath $(dir $(THISFILE))/../..)
+
+# Build directories
+# Note that the debug/test/coverage directories are defined in relation
+# to the release directory (BUILD_DIR), this needs to be kept in mind
+# if over-riding it and building those widget types, the specific widget
+# type variable (e.g. BUILD_DIR_DEBUG) may also need to be specified
+# to yield the desired output hierarchy.
+BUILD_DIR = $(ROOT_DIR)/build
+BUILD_DIR_DEBUG = $(abspath $(BUILD_DIR)/../build-debug)
+BUILD_DIR_TEST = $(abspath $(BUILD_DIR)/../build-test)
+BUILD_DIR_COVERAGE = $(abspath $(BUILD_DIR)/../build-coverage)
+
+# Output directory variable for use in pattern rules.
+# This is intended for internal use only, hence the explicit override
+# definition.
+override OUTPUT_DIR = $(BUILD_DIR)
+
+# Final install directory for widgets
+DEST = $(OUTPUT_DIR)
+
+# Default build type for release/test builds
+BUILD_TYPE = RELEASE
+
+.PHONY: all help update install distclean
+.PHONY: clean clean-release clean-debug clean-test clean-coverage clean-all
+.PHONY: configure configure-release configure-debug configure-test configure-coverage
+.PHONY: build build-release build-debug build-test build-coverage build-all
+.PHONY: package package-release package-debug package-test package-coverage package-all
+
+help:
+       @echo "List of targets available:"
+       @echo ""
+       @echo "- all"
+       @echo "- help"
+       @echo "- clean"
+       @echo "- distclean"
+       @echo "- configure"
+       @echo "- build: compilation, link and prepare files for package into a widget"
+       @echo "- package: output a widget file '*.wgt'"
+       @echo "- install: install in your $(CMAKE_INSTALL_DIR) directory"
+       @echo ""
+       @echo "Usage: ./autobuild/agl/autobuild package DEST=${HOME}/opt"
+       @echo "Don't use your build dir as DEST as wgt file is generated at this location"
+
+all: package-all
+
+# Target specific variable over-rides so static pattern rules can be
+# used for the various type-specific targets.
+
+configure-test build-test package-test clean-test: OUTPUT_DIR = $(BUILD_DIR_TEST)
+
+configure-coverage build-coverage package-coverage clean-coverage: OUTPUT_DIR = $(BUILD_DIR_COVERAGE)
+configure-coverage build-coverage package-coverage: BUILD_TYPE = COVERAGE
+
+configure-debug build-debug package-debug clean-debug: OUTPUT_DIR = $(BUILD_DIR_DEBUG)
+configure-debug build-debug package-debug: BUILD_TYPE = DEBUG
+
+clean-release clean-test clean-debug clean-coverage:
+       @if [ -d $(OUTPUT_DIR) ]; then \
+               $(MAKE) -C $(OUTPUT_DIR) $(CLEAN_ARGS) clean; \
+       else \
+               echo Nothing to clean; \
+       fi
+
+clean: clean-release
+
+clean-all: clean-release clean-test clean-debug clean-coverage
+
+distclean: clean-all
+
+configure-release configure-test configure-debug configure-coverage:
+       @mkdir -p $(OUTPUT_DIR)
+       @if [ ! -f $(OUTPUT_DIR)/Makefile ]; then \
+               (cd $(OUTPUT_DIR) && cmake -S $(ROOT_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) $(CONFIGURE_ARGS)); \
+       fi
+
+configure: configure-release
+
+build-release build-debug build-coverage: build-%: configure-%
+       @cmake --build $(OUTPUT_DIR) $(BUILD_ARGS) --target all
+
+# Kept for consistency, empty to avoid building everything for test widget
+build-test: configure-test
+
+build: build-release
+
+build-all: build-release build-debug build-test build-coverage
+
+package-release package-debug package-coverage: package-%: build-%
+       @cmake --build $(OUTPUT_DIR) $(PACKAGE_ARGS) --target widget
+       @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \
+               mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \
+       fi
+
+package-test: build-test
+       @cmake --build $(OUTPUT_DIR) $(PACKAGE_ARGS) --target test_widget
+       @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \
+               mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \
+       fi
+
+package: package-release
+
+package-all: package-release package-test package-coverage package-debug
+
+update: configure
+       @cmake --build $(BUILD_DIR) --target autobuild
+
+install: build
+       @cmake --build $(BUILD_DIR) $(INSTALL_ARGS) --target install
diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake
new file mode 100644 (file)
index 0000000..4549e42
--- /dev/null
@@ -0,0 +1,205 @@
+###########################################################################
+# Copyright 2015-2018 IoT.bzh
+# Copyright 2018,2019 Konsulko Group
+# Copyright 2021 Collabora Ltd.
+#
+# author: Fulup Ar Foll <fulup@iot.bzh>
+# camera-gstreamer: 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 Info
+# ------------------
+set(PROJECT_NAME camera-gstreamer)
+set(PROJECT_PRETTY_NAME "Camera gtreamer")
+set(PROJECT_DESCRIPTION "Streaming camera feed using gstreamer native application")
+set(PROJECT_URL "https://gerrit.automotivelinux.org/gerrit/app/camera-gstreamer.git")
+set(PROJECT_VERSION "1.0")
+set(PROJECT_ICON "icon.png")
+set(PROJECT_AUTHOR "Marius Vlad")
+set(PROJECT_AUTHOR_MAIL "marius.vlad@collabora.com")
+set(PROJECT_LICENSE "Apache 2.0")
+set(PROJECT_LANGUAGES "CXX")
+
+# Where are stored the project configuration files
+# relative to the root project directory
+set(PROJECT_CMAKE_CONF_DIR "conf.d")
+
+# Where are stored your external libraries for your project. This is 3rd party library that you don't maintain
+# but used and must be built and linked.
+# set(PROJECT_LIBDIR "libs")
+
+# Which directories inspect to find CMakeLists.txt target files
+# set(PROJECT_SRC_DIR_PATTERN "*")
+
+# Compilation Mode (DEBUG, RELEASE)
+# ----------------------------------
+#set(CMAKE_BUILD_TYPE "DEBUG")
+set(USE_EFENCE 1)
+
+# Kernel selection if needed. You can choose between a
+# mandatory version to impose a minimal version.
+# Or check Kernel minimal version and just print a Warning
+# about missing features and define a preprocessor variable
+# to be used as preprocessor condition in code to disable
+# incompatibles features. Preprocessor define is named
+# KERNEL_MINIMAL_VERSION_OK.
+#
+# NOTE*** FOR NOW IT CHECKS KERNEL Yocto environment and
+# Yocto SDK Kernel version.
+# -----------------------------------------------
+#set (kernel_mandatory_version 4.8)
+#set (kernel_minimal_version 4.8)
+
+# Compiler selection if needed. Impose a minimal version.
+# -----------------------------------------------
+set (gcc_minimal_version 4.9)
+
+# PKG_CONFIG required packages
+# -----------------------------
+set (PKG_REQUIRED_LIST
+       json-c
+       afb-daemon
+)
+
+# Prefix path where will be installed the files
+# Default: /usr/local (need root permission to write in)
+# ------------------------------------------------------
+#set(CMAKE_INSTALL_PREFIX $ENV{HOME}/opt)
+
+# Customize link option
+# -----------------------------
+#list(APPEND link_libraries -an-option)
+
+# Compilation options definition
+# Use CMake generator expressions to specify only for a specific language
+# Values are prefilled with default options that is currently used.
+# Either separate options with ";", or each options must be quoted separately
+# DO NOT PUT ALL OPTION QUOTED AT ONCE , COMPILATION COULD FAILED !
+# ----------------------------------------------------------------------------
+#set(COMPILE_OPTIONS
+# -Wall
+# -Wextra
+# -Wconversion
+# -Wno-unused-parameter
+# -Wno-sign-compare
+# -Wno-sign-conversion
+# -Werror=maybe-uninitialized
+# -Werror=implicit-function-declaration
+# -ffunction-sections
+# -fdata-sections
+# -fPIC
+# CACHE STRING "Compilation flags")
+#set(C_COMPILE_OPTIONS "" CACHE STRING "Compilation flags for C language.")
+#set(CXX_COMPILE_OPTIONS "" CACHE STRING "Compilation flags for C++ language.")
+#set(PROFILING_COMPILE_OPTIONS
+# -g
+# -O0
+# -pg
+# -Wp,-U_FORTIFY_SOURCE
+# CACHE STRING "Compilation flags for PROFILING build type.")
+#set(DEBUG_COMPILE_OPTIONS
+# -g
+# -ggdb
+# -Wp,-U_FORTIFY_SOURCE
+# CACHE STRING "Compilation flags for DEBUG build type.")
+#set(CCOV_COMPILE_OPTIONS
+# -g
+# -O2
+# --coverage
+# CACHE STRING "Compilation flags for CCOV build type.")
+#set(RELEASE_COMPILE_OPTIONS
+# -g
+# -O2
+# CACHE STRING "Compilation flags for RELEASE build type.")
+
+# (BUG!!!) as PKG_CONFIG_PATH does not work [should be an env variable]
+# ---------------------------------------------------------------------
+set(CMAKE_PREFIX_PATH ${CMAKE_INSTALL_PREFIX}/lib64/pkgconfig ${CMAKE_INSTALL_PREFIX}/lib/pkgconfig)
+set(LD_LIBRARY_PATH ${CMAKE_INSTALL_PREFIX}/lib64 ${CMAKE_INSTALL_PREFIX}/lib)
+
+# Optional location for config.xml.in
+# -----------------------------------
+#set(WIDGET_ICON "\"conf.d/wgt/${PROJECT_ICON}\"" CACHE PATH "Path to the widget icon")
+set(WIDGET_CONFIG_TEMPLATE "${CMAKE_CURRENT_SOURCE_DIR}/conf.d/wgt/config.xml.in" CACHE PATH "Path to widget config file template (config.xml.in)")
+
+# Mandatory widget Mimetype specification of the main unit
+# --------------------------------------------------------------------------
+# Choose between :
+#- text/html : HTML application,
+#      content.src designates the home page of the application
+#
+#- application/vnd.agl.native : AGL compatible native,
+#      content.src designates the relative path of the binary.
+#
+# - application/vnd.agl.service: AGL service, content.src is not used.
+#
+#- ***application/x-executable***: Native application,
+#      content.src designates the relative path of the binary.
+#      For such application, only security setup is made.
+#
+set(WIDGET_TYPE application/vnd.agl.native)
+
+# Mandatory Widget entry point file of the main unit
+# --------------------------------------------------------------
+# This is the file that will be executed, loaded,
+# at launch time by the application framework.
+#
+set(WIDGET_ENTRY_POINT bin/camera-gstreamer)
+
+# Optional dependencies order
+# ---------------------------
+#set(EXTRA_DEPENDENCIES_ORDER)
+
+# Optional Extra global include path
+# -----------------------------------
+#set(EXTRA_INCLUDE_DIRS)
+
+# Optional extra libraries
+# -------------------------
+#set(EXTRA_LINK_LIBRARIES)
+
+# Optional force binding Linking flag
+# ------------------------------------
+# set(BINDINGS_LINK_FLAG LinkOptions )
+
+# Optional force package prefix generation, like widget
+# -----------------------------------------------------
+# set(PKG_PREFIX DestinationPath)
+
+# Optional Application Framework security token
+# and port use for remote debugging.
+#------------------------------------------------------------
+set(AFB_TOKEN   ""     CACHE PATH "Default binder security token")
+set(AFB_REMPORT "1234" CACHE PATH "Default binder listening port")
+
+# Print a helper message when every thing is finished
+# ----------------------------------------------------
+set(CLOSING_MESSAGE "Typical binding launch: afb-daemon --port=${AFB_REMPORT} --workdir=${CMAKE_BINARY_DIR}/package --ldpaths=lib --roothttp=htdocs  --token=\"${AFB_TOKEN}\" --tracereq=common --verbose")
+set(PACKAGE_MESSAGE "Install widget file using in the target : afm-util install ${PROJECT_NAME}.wgt")
+
+# Optional schema validator about now only XML, LUA and JSON
+# are supported
+#------------------------------------------------------------
+#set(LUA_CHECKER "luac" "-p" CACHE STRING "LUA compiler")
+#set(XML_CHECKER "xmllint" CACHE STRING "XML linter")
+#set(JSON_CHECKER "json_verify" CACHE STRING "JSON linter")
+
+# This include is mandatory and MUST happens at the end
+# of this file, else you expose you to unexpected behavior
+#
+# This CMake module could be found at the following url:
+# https://gerrit.automotivelinux.org/gerrit/#/admin/projects/src/cmake-apps-module
+# -----------------------------------------------------------
+include(CMakeAfbTemplates)
diff --git a/conf.d/wgt/config.xml.in b/conf.d/wgt/config.xml.in
new file mode 100644 (file)
index 0000000..9550cb5
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widget xmlns="http://www.w3.org/ns/widgets" id="@PROJECT_NAME@" version="@PROJECT_VERSION@">
+  <name>@PROJECT_NAME@</name>
+  <icon src="@PROJECT_ICON@"/>
+  <content src="@WIDGET_ENTRY_POINT@" type="@WIDGET_TYPE@"/>
+  <description>@PROJECT_DESCRIPTION@</description>
+  <author>@PROJECT_AUTHOR@ &lt;@PROJECT_AUTHOR_MAIL@&gt;</author>
+  <license>@PROJECT_LICENSE@</license>
+  <feature name="urn:AGL:widget:required-permission">
+    <param name="urn:AGL:permission::public:display" value="required" />
+    <param name="urn:AGL:permission::public:no-htdocs" value="required" />
+  </feature>
+</widget>