main: Remove fullscreen option from waylandsink
[apps/camera-gstreamer.git] / app / main.cpp
index d4c0671..64a435b 100644 (file)
 #include <fcntl.h>
 #include <assert.h>
 
-#include "xdg-shell-client-protocol.h"
-#include "agl-shell-desktop-client-protocol.h"
 #include "utils.h"
+#include "xdg-shell-client-protocol.h"
+#include "AglShellGrpcClient.h"
 
 #include <gst/gst.h>
 
 #include <gst/video/videooverlay.h>
 #include <gst/wayland/wayland.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
+#endif
+
+#ifndef APP_DATA_PATH
+#define APP_DATA_PATH /usr/share/applications/data
+#endif
+
 // 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
@@ -31,6 +40,8 @@
 #define WINDOW_WIDTH_POS_X     640
 #define WINDOW_WIDTH_POS_Y     180
 
+#define MAX_BUFFER_ALLOC       2
+
 // 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; \
@@ -50,8 +61,6 @@ struct display {
        } output_data;
 
        struct xdg_wm_base *wm_base;
-       struct agl_shell_desktop *agl_shell_desktop;
-
        int has_xrgb;
 };
 
@@ -59,6 +68,9 @@ struct buffer {
        struct wl_buffer *buffer;
        void *shm_data;
        int busy;
+       int width, height;
+       size_t size;    /* width * 4 * height */
+       struct wl_list buffer_link; /** window::buffer_list */
 };
 
 struct window {
@@ -66,6 +78,8 @@ struct window {
 
        int x, y;
        int width, height;
+       int init_width;
+       int init_height;
 
        struct wl_surface *surface;
 
@@ -75,8 +89,9 @@ struct window {
 
        int fullscreen, maximized;
 
-       struct buffer buffers[2];
+       struct wl_list buffer_list;
        struct wl_callback *callback;
+       bool needs_update_buffer;
 };
 
 
@@ -84,15 +99,68 @@ struct receiver_data {
        struct window *window;
 
        GstElement *pipeline;
-       GstWaylandVideo *wl_video;
        GstVideoOverlay *overlay;
 };
 
 static int running = 1;
+static bool gst_pipeline_failed = FALSE;
+static bool fallback_gst_pipeline_tried = FALSE;
 
 static void
 redraw(void *data, struct wl_callback *callback, uint32_t time);
 
+static struct buffer *
+alloc_buffer(struct window *window, int width, int height)
+{
+       struct buffer *buffer = static_cast<struct buffer *>(calloc(1, sizeof(*buffer)));
+
+       buffer->width = width;
+       buffer->height = height;
+       wl_list_insert(&window->buffer_list, &buffer->buffer_link);
+
+       return buffer;
+}
+
+static void
+destroy_buffer(struct buffer *buffer)
+{
+       if (buffer->buffer)
+               wl_buffer_destroy(buffer->buffer);
+
+       munmap(buffer->shm_data, buffer->size);
+       wl_list_remove(&buffer->buffer_link);
+       free(buffer);
+}
+
+static struct buffer *
+pick_free_buffer(struct window *window)
+{
+       struct buffer *b;
+       struct buffer *buffer = NULL;
+
+       wl_list_for_each(b, &window->buffer_list, buffer_link) {
+               if (!b->busy) {
+                       buffer = b;
+                       break;
+               }
+       }
+
+       return buffer;
+}
+
+static void
+prune_old_released_buffers(struct window *window)
+{
+       struct buffer *b, *b_next;
+
+       wl_list_for_each_safe(b, b_next,
+                             &window->buffer_list, buffer_link) {
+               if (!b->busy && (b->width != window->width ||
+                                b->height != window->height))
+                       destroy_buffer(b);
+       }
+}
+
 static void
 paint_pixels(void *image, int padding, int width, int height, uint32_t time)
 {
@@ -144,7 +212,12 @@ create_shm_buffer(struct display *display, struct buffer *buffer,
        close(fd);
 
        buffer->shm_data = data;
+       buffer->size = size;
+       buffer->width = width;
+       buffer->height = height;
+
        fprintf(stdout, "Created shm buffer with width %d, height %d\n", width, height);
+
        return 0;
 }
 
@@ -154,26 +227,19 @@ 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) {
-               /* The 'old' buffer might not exist if maximized is received
-                * from the start. */
-               if (window->buffers[0].buffer && !window->buffers[0].busy) {
-                       wl_buffer_destroy(window->buffers[0].buffer);
-                       window->buffers[0].buffer = NULL;
-                       window->wait_for_configure = false;
-               }
+       if (window->needs_update_buffer) {
+               int i;
+
+               for (i = 0; i < MAX_BUFFER_ALLOC; i++)
+                       alloc_buffer(window, window->width, window->height);
+
+               window->needs_update_buffer = false;
        }
 
-       if (!window->buffers[0].busy) {
-               buffer = &window->buffers[0];
-       } else if (!window->buffers[1].busy) {
-               buffer = &window->buffers[1];
-       } else {
+       buffer = pick_free_buffer(window);
+       if (!buffer)
                return NULL;
-       }
+
 
        if (!buffer->buffer) {
                ret = create_shm_buffer(window->display, buffer, window->width,
@@ -197,31 +263,33 @@ static const struct wl_callback_listener frame_listener = {
 static void
 redraw(void *data, struct wl_callback *callback, uint32_t time)
 {
-        struct window *window = static_cast<struct window *>(data);
-        struct buffer *buffer;
+       struct window *window = static_cast<struct window *>(data);
+       struct buffer *buffer;
+
+       prune_old_released_buffers(window);
 
-        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();
-        }
+       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);
+       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);
+       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);
+       window->callback = wl_surface_frame(window->surface);
+       wl_callback_add_listener(window->callback, &frame_listener, window);
+       wl_surface_commit(window->surface);
 
-        buffer->busy = 1;
+       buffer->busy = 1;
 }
 
 static void
@@ -301,32 +369,6 @@ static const struct wl_output_listener output_listener = {
        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,
@@ -346,12 +388,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));
@@ -401,12 +437,11 @@ 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)) {
+       if (gst_is_wl_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));
+               context = gst_wl_display_handle_context_new(display_handle);
                gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(message)), context);
 
                goto drop;
@@ -432,6 +467,19 @@ bus_sync_handler(GstBus *bus, GstMessage *message, gpointer user_data)
 
                goto drop;
        }
+       else if (GST_MESSAGE_TYPE(message) == GST_MESSAGE_ERROR) {
+               GError* err = NULL;
+               gchar* dbg_info = NULL;
+
+               gst_message_parse_error(message, &err, &dbg_info);
+               g_printerr("ERROR from element %s: %s code %d\n",
+                       GST_OBJECT_NAME(message->src), err->message, err->code);
+               g_printerr("Debugging info: %s\n", (dbg_info) ? dbg_info : "none");
+               gst_pipeline_failed = TRUE;
+               g_error_free(err);
+               g_free(dbg_info);
+               goto drop;
+       }
 
        return GST_BUS_PASS;
 
@@ -449,6 +497,7 @@ handle_xdg_surface_configure(void *data, struct xdg_surface *surface, uint32_t s
 
        if (window->wait_for_configure) {
                redraw(window, NULL, 0);
+               window->wait_for_configure = false;
        }
 }
 
@@ -469,7 +518,7 @@ handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
 
        // use our own macro as C++ can't typecast from (void *) directly
        WL_ARRAY_FOR_EACH(p, states, uint32_t *) {
-               uint32_t state = *p; 
+               uint32_t state = *p;
                switch (state) {
                case XDG_TOPLEVEL_STATE_FULLSCREEN:
                        window->fullscreen = 1;
@@ -482,29 +531,18 @@ handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
 
        if (width > 0 && height > 0) {
                if (!window->fullscreen && !window->maximized) {
-                       window->width = width;
-                       window->height = height;
+                       window->init_width = width;
+                       window->init_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;
+               window->width = window->init_width;
+               window->height = window->init_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;
-       }
+       window->needs_update_buffer = true;
+
 }
 
 static void
@@ -522,6 +560,7 @@ static struct window *
 create_window(struct display *display, int width, int height, const char *app_id)
 {
        struct window *window;
+       int i;
 
        assert(display->wm_base != NULL);
 
@@ -529,10 +568,13 @@ create_window(struct display *display, int width, int height, const char *app_id
        if (!window)
                return NULL;
 
+       wl_list_init(&window->buffer_list);
        window->callback = NULL;
        window->display = display;
        window->width = width;
        window->height = height;
+       window->init_width = width;
+       window->init_height = height;
        window->surface = wl_compositor_create_surface(display->wl_compositor);
 
        if (display->wm_base) {
@@ -554,6 +596,9 @@ create_window(struct display *display, int width, int height, const char *app_id
                window->wait_for_configure = true;
        }
 
+       for (i = 0; i < MAX_BUFFER_ALLOC; i++)
+               alloc_buffer(window, window->width, window->height);
+
        return window;
 }
 
@@ -561,13 +606,14 @@ create_window(struct display *display, int width, int height, const char *app_id
 static void
 destroy_window(struct window *window)
 {
+       struct buffer *buffer, *buffer_next;
+
        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);
+       wl_list_for_each_safe(buffer, buffer_next,
+                             &window->buffer_list, buffer_link)
+               destroy_buffer(buffer);
 
        if (window->xdg_toplevel)
                xdg_toplevel_destroy(window->xdg_toplevel);
@@ -609,11 +655,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) {
@@ -633,9 +674,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);
 
@@ -645,82 +683,117 @@ destroy_display(struct display *display)
        free(display);
 }
 
-int main(int argc, char *argv[])
+// stringify the un-quoted string to quoted C string
+#define xstr(a) str(a)
+#define str(a) #a
+
+GstElement* create_pipeline(int* argc, char** argv[])
 {
-       int ret = 0;
-       struct sigaction sa;
-       struct receiver_data receiver_data = {};
-       struct display *display;
-       struct window *window;
+       GError *error = NULL;
        const char *camera_device = NULL;
        const char *width_str = NULL;
        const char *height_str = NULL;
        int width;
        int height;
 
-       char pipeline_str[1024];
-       GError *error = NULL;
-       const char *app_id = "camera-gstreamer";
+       // pipewire is default.
+       char *v4l2_path = getenv("ENABLE_V4L2_PATH");
+       bool v4l2 = false;
 
-       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");
+       char pipeline_str[1024];
 
        camera_device = getenv("DEFAULT_V4L2_DEVICE");
        if (!camera_device)
                camera_device = get_first_camera_device();
-       width_str = getenv("DEFAULT_V4L2_DEVICE_WIDTH");
+
+       width_str = getenv("DEFAULT_DEVICE_WIDTH");
        if (!width_str)
                width = WINDOW_WIDTH_SIZE;
        else
                width = atoi(width_str);
 
-       height_str = getenv("DEFAULT_V4L2_DEVICE_HEIGHT");
+       height_str = getenv("DEFAULT_DEVICE_HEIGHT");
        if (!height_str)
                height = WINDOW_HEIGHT_SIZE;
        else
                height = atoi(height_str);
 
+       if (v4l2_path == NULL)
+               v4l2 = false;
+       else if (g_str_equal(v4l2_path, "yes") || g_str_equal(v4l2_path, "true"))
+               v4l2 = true;
+
        memset(pipeline_str, 0, sizeof(pipeline_str));
-       snprintf(pipeline_str, sizeof(pipeline_str), "v4l2src device=%s ! video/x-raw,width=%d,height=%d ! waylandsink", 
-               camera_device, width, height);
-       gst_init(&gargc, &gargv);
 
-       setbuf(stdout, NULL);
+       if (v4l2)
+               snprintf(pipeline_str, sizeof(pipeline_str), "v4l2src device=%s ! video/x-raw,width=%d,height=%d ! waylandsink",
+                       camera_device, width, height);
+       else if (gst_pipeline_failed == TRUE) {
+               snprintf(pipeline_str, sizeof(pipeline_str), "filesrc location=%s/still-image.jpg ! decodebin ! videoconvert ! imagefreeze ! waylandsink",
+                       xstr(APP_DATA_PATH));
+               fallback_gst_pipeline_tried = TRUE;
+       }
+       else {
+               snprintf(pipeline_str, sizeof(pipeline_str), "pipewiresrc ! waylandsink");
+       }
 
        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(gargv);
-               return EXIT_FAILURE;
+               free(argv);
+               return NULL;
        }
 
-       receiver_data.pipeline = pipeline;
+       return pipeline;
+}
+
+int main(int argc, char* argv[])
+{
+       int ret = 0;
+       struct sigaction sa;
+       struct receiver_data receiver_data = {};
+       struct display* display;
+       struct window* window;
+       const char* app_id = "camera-gstreamer";
+
+       // for starting the application from the beginning, with a diffrent
+       // role we need to handle that creating the main window
+       if (argc >= 2 && strcmp(argv[1], "float") == 0) {
+               GrpcClient *client = new GrpcClient();
+               client->SetAppFloat(std::string(app_id), 30, 400);
+       }
+
+       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");
+
+       setbuf(stdout, NULL);
+
+       gst_init(&gargc, &gargv);
+
+       receiver_data.pipeline = create_pipeline(&gargc, &gargv);
+
+       if (!receiver_data.pipeline)
+               return EXIT_FAILURE;
 
        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); 
+       window = create_window(display, WINDOW_WIDTH_SIZE, WINDOW_HEIGHT_SIZE, app_id);
 
        if (!window) {
                free(gargv);
@@ -738,25 +811,38 @@ int main(int argc, char *argv[])
                redraw(window, NULL, 0);
        }
 
-       GstBus *bus = gst_element_get_bus(pipeline);
+       GstBus *bus = gst_element_get_bus(receiver_data.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);
+       gst_element_set_state(receiver_data.pipeline, GST_STATE_PLAYING);
        fprintf(stdout, "gstreamer pipeline running\n");
 
        // run the application
-       while (running && ret != -1)
+       while (running && ret != -1) {
                ret = wl_display_dispatch(display->wl_display);
+               if (gst_pipeline_failed && fallback_gst_pipeline_tried == FALSE) {
+                       gst_element_set_state(receiver_data.pipeline, GST_STATE_NULL);
+                       gst_object_unref(receiver_data.pipeline);
+                       /* retry with fallback pipeline */
+                       receiver_data.pipeline = create_pipeline(&gargc, &gargv);
+                       GstBus *bus = gst_element_get_bus(receiver_data.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(receiver_data.pipeline, GST_STATE_PLAYING);
+               }
+       }
+       gst_element_set_state(receiver_data.pipeline, GST_STATE_NULL);
+       gst_object_unref(receiver_data.pipeline);
 
        destroy_window(window);
        destroy_display(display);
        free(gargv);
 
-       gst_element_set_state(pipeline, GST_STATE_NULL);
-       gst_object_unref(pipeline);
        return ret;
 }