#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>
-#define DEFAULT_VIDEO_DEVICE "/dev/video0"
+#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_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; \
} output_data;
struct xdg_wm_base *wm_base;
- struct agl_shell_desktop *agl_shell_desktop;
-
int has_xrgb;
};
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 {
int x, y;
int width, height;
+ int init_width;
+ int init_height;
struct wl_surface *surface;
int fullscreen, maximized;
- struct buffer buffers[2];
+ struct wl_list buffer_list;
struct wl_callback *callback;
+ bool needs_update_buffer;
};
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)
{
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;
}
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->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,
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
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,
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));
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;
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;
if (window->wait_for_configure) {
redraw(window, NULL, 0);
+ window->wait_for_configure = false;
}
}
// 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;
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
create_window(struct display *display, int width, int height, const char *app_id)
{
struct window *window;
+ int i;
assert(display->wm_base != NULL);
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) {
window->wait_for_configure = true;
}
+ for (i = 0; i < MAX_BUFFER_ALLOC; i++)
+ alloc_buffer(window, window->width, window->height);
+
return window;
}
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);
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) {
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);
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[])
+{
+ GError *error = NULL;
+ const char *camera_device = NULL;
+ const char *width_str = NULL;
+ const char *height_str = NULL;
+ int width;
+ int height;
+
+ // pipewire is default.
+ char *v4l2_path = getenv("ENABLE_V4L2_PATH");
+ bool v4l2 = false;
+
+ char pipeline_str[1024];
+
+ camera_device = getenv("DEFAULT_V4L2_DEVICE");
+ if (!camera_device)
+ camera_device = get_first_camera_device();
+
+ width_str = getenv("DEFAULT_DEVICE_WIDTH");
+ if (!width_str)
+ width = WINDOW_WIDTH_SIZE;
+ else
+ width = atoi(width_str);
+
+ 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));
+
+ 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(argv);
+ return NULL;
+ }
+
+ 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;
- char pipeline_str[1024];
- GError *error = NULL;
- const char *app_id = "camera-gstreamer";
+ 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);
sigaction(SIGINT, &sa, NULL);
int gargc = 2;
- char **gargv = static_cast<char **>(calloc(2, sizeof(char *)));
+ 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);
+ setbuf(stdout, NULL);
+
gst_init(&gargc, &gargv);
- fprintf(stdout, "Using pipeline: %s\n", pipeline_str);
+ receiver_data.pipeline = create_pipeline(&gargc, &gargv);
- GstElement *pipeline = gst_parse_launch(pipeline_str, &error);
- if (error || !pipeline) {
- fprintf(stderr, "gstreamer pipeline construction failed!\n");
- free(argv);
+ if (!receiver_data.pipeline)
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);
+ window = create_window(display, WINDOW_WIDTH_SIZE, WINDOW_HEIGHT_SIZE, app_id);
if (!window) {
- free(argv);
+ free(gargv);
return EXIT_FAILURE;
}
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(argv);
+ free(gargv);
- gst_element_set_state(pipeline, GST_STATE_NULL);
- gst_object_unref(pipeline);
return ret;
}